发票查验 Skill
概述
本 Skill 提供基于数族臻票云自有查验接口的发票查验能力。用户在对话中输入发票信息或上传发票文件后,自动识别票种、提取查验字段、判断字段是否完整、调用臻票云 /v2/zpy/invoice/zyfpcy 接口、解析并返回结构化结果。全程在对话中完成,无需用户跳转外部平台。
> 关键说明:本 skill 的查验能力完全由我们自己的后端接口承载,调用方式走 AUTH_KEYS 网关(X-AUTH-KEY 鉴权),与发票统计 / 臻票云经营简报共用同一份凭证。
无 AUTH_KEY 时的引导流程(老商户 vs 新商户)
当用户没有可用凭证(~/.workbuddy/credentials/auth_keys.json 不存在 / 为空 / 解析失败)时,不要直接报错就结束,先判断其身份再走对应分支:
- 先确认身份:问一句「您是否已在商户中心(shanghu.shuzutech.com)注册过账号?」或根据上下文判断("我之前配过 key""老账号"→ 老商户;"我是新开的""第一次用"→ 新商户)。
- 老商户(已有账号,只是没配 / 丢了 apiKey) → 引导其提供 apiKey:
- 让用户在商户中心 开发者 / API 密钥 复制 AUTH_KEY,直接发给我,我来写入凭证文件;或
- 用户自行运行:python3 scripts/verify.py set-auth-key --api-key <密钥> --alias <商户名>。
- 写入后即标记为 active,可直接verify/quota,无需重新登录。 - 新商户(从未注册过) → 引导注册,注册成功自动获取并写入 AUTH_KEY:
-register-captcha取图形验证码 → 展示给用户 → 用户填写图片字符;
-register-send-code --phone <手机号> --captcha <字符>发送短信验证码;
-register --phone <手机号> --password <密码> --phone-code <短信码> --token <上步token> [--alias <商户名>]。
- 注册成功即自动获得 AUTH_KEY 并写入凭证,无需再手动配置。
- 注册后完善商户信息(领取免费查验额度):凭据已写入,继续引导
match-title --qymc <企业名称>做抬头联想、返回匹配企业列表让用户选其一,
再用fill-merchant-info --nsrmc <所选名称> --nsrsbh <所选纳税人识别号>补全资料。
仅当fill-merchant-info成功返回时才提示「已到账 10 次免费查验额度」;
register本身不提示额度。
- 完善企业信息前不允许查验(门禁):新商户注册成功后、在fill-merchant-info
成功之前,脚本会直接拦截verify(提示先用两步完善企业信息),不会发起查验请求。
match-title是完善流程的一部分,不受拦截。老商户(set-auth-key写入)与本门禁
上线前注册的商户均无此状态记录,不受影响。
> 路由规则:业务 /v2 接口(查验、余额查询,以及注册后完善信息的「抬头联想 /v2/zpy/invoice/quandian/match/enterprise/title」、「完善商户信息 /v2/zpy/invoice/merchant/fill/info」)统一走 AUTH_KEYS 网关(X-AUTH-KEY);
> 注册相关非 /v2 路径(图形验证码、发短信、手机号注册)直接拼商户中心域名调用;
> 注册阶段的 /v2/zpy/invoice/merchant/auth-keys 用注册登录得到的 Bearer 会话鉴权(此时尚无 AUTH_KEY),故直接拼商户中心域名 + Bearer 调用,不进网关。
适用场景(意图识别)
进入发票查验流程的用户表达示例:
- "查一下这张发票真假" / "帮我验一下发票"
- "这张票存在吗" / "这张发票是不是正常"
- "这张票是不是作废了" / "是不是红冲了"
- "发票号码是 xxx,开票日期是 xxx,价税合计 xxx,帮我查验"
- "我上传一张发票,帮我查一下"
- "我没配过 apiKey / 我的 key 找不到了,怎么用"
- "我是新商户,第一次用,怎么开通"
- "帮我注册 / 我还没在商户中心注册过"
不进入发票查验流程的表达:开票 / 红冲 / 重开、税收分类编码咨询、税率咨询、报销规则咨询、"这张票能不能抵扣"。
> 当进入流程但尚未配置凭证时,按上方「无 AUTH_KEY 时的引导流程」先完成老商户给 key / 新商户注册,再进入查验。
输入方式
支持三种录入,最终都统一成 cyList 入参交给 verify.py 执行:
- 附件上传(图片 / PDF / OFD):agent 用 Read 工具读取票面,提取每张发票的
fphm / fpdm / kprq / je / jshj / jym;字段不清时向用户确认,不要臆造。识别清楚后组装 JSON 调用查验。 - 字段录入:用户自然语言或结构化给出发票代码 / 号码 / 日期 / 金额。支持单张(
--fphm/--kprq/--je/--jshj/--fpdm/--jym)或多张(--file/--json,兼容invoices/cyList/ 裸列表)。 - 一次多张:一次可提交多张发票,自动逐张识别票种并查验。
自动识别票种:依据发票代码 fpdm 第 8 位 best-effort 预判票种;权威票种以查验响应里的 fplx 为准(数电票 fpdm 留空)。
数电票(fpdm 为空)请显式提供票种代码 fplxdm(如 0920 数电专票、0904 数电纸质普票),以便脚本按票种判定 je/jym 是否必填;不提供则无法预校验,将由接口实际返回决定。
参数校验
参数不完整或格式错误时,逐条中文提示缺什么、怎么补,绝不带错参上送接口:
- 缺开票日期(
kprq)→ 提示补充,格式YYYY-MM-DD(仅日期,不含时分秒) - 金额 / 价税合计非数字 → 提示修正(负数允许,红字发票即为负数)
- 价税合计的绝对值 < 金额的绝对值 → 提示核对
- 按票种必填(查验前判定,已知票种时强制):
je金额必填:增值税专用发票、增值税电子专用发票、机动车销售统一发票、数电纸质(增值税专用发票)、数电纸质(机动车销售统一发票)、二手车统一销售发票(其je即「车价合计」),及上述票种的数电/纸质全变体(含0920/0901/0903/0930/15/0915/0940)。jym校验码必填:增值税普通发票、增值税电子普通发票、增值税普通发票(卷式)、增值税电子普通发票(通行费)、数电纸质(增值税普通发票)(含04/10/11/14/0904)。- 票种由
fplxdm(用户提供)优先,其次由fpdm第 8 位推断;数电票需显式fplxdm才能判定。 - 票种无法判断 → 让用户补充发票类型,或上传完整发票文件
校验通过才调用接口。
查验流程(调用我们自己的接口)
> 前置门禁(新商户):由注册流程创建且尚未完成 fill-merchant-info 的凭证,
> 调用 verify 会被直接拦截并提示先完善企业信息,不会请求查验接口、不消耗额度。
> 详见「无 AUTH_KEY 时的引导流程」第 3 点。
字段校验通过后,通过 AUTH_KEYS 网关调用:
- 接口:
POST /v2/zpy/invoice/zyfpcy - 入参:
{"cyList":[{"fphm","fpdm","jym","je","jshj","kprq"}, ...]} - 鉴权:密钥只在请求头
X-AUTH-KEY,业务路径写在 body 的apiPath - 网关入口:
https://shanghu.shuzutech.com/api/open/invoke
调用示例(由 agent 执行,对用户只需说"帮我查这张发票"):
# 单张:数电票 fpdm 留空
python3 scripts/verify.py verify --fphm 23512000000020981613 --kprq "2023-03-29" --je 1415.09 --jshj 1500.00
# 多张:从 JSON 文件读取(支持 {"invoices":[...]} / {"cyList":[...]} / 裸列表)
python3 scripts/verify.py verify --file invoices.json
# 指定凭证主体
python3 scripts/verify.py --key 总店 verify --file invoices.json
完整执行细节(鉴权、请求/响应结构、结果判定、附件流程)见 references/usage.md;凭证配置见 references/setup.md。
结果判定(逐票)
- ✅ 验真通过:
code=="0" 且 success==true 且返回票面→ 展示票种、发票状态、购销方、金额/税额/价税合计、货物明细。 - 展示约定:开票日期只展示到年月日(YYYY-MM-DD),不附带时分秒;发票状态只展示中文名(如 正常/作废/红冲),不展示
fpztDm代码;HTML 卡片不展示发票号码(隐私保护),仅以「第 N 张」序号标识。 - ⚠️ 无法识别 / 查无此票:响应
msg含"查无 / 不存在 / 无此 / 未找到" → 不直接判定假票,提示核对或联系开票方。 - ❌ 查验不通过:其他
success==false或code!="0"→ 附原因。 - ❓ 无法判定:结构异常时如实转述。
- 状态告警:发票状态
fpztDm非"0"(作废 / 红冲等)时,即便票面一致也提示通常不可用于报销 / 入账。
余额 / 到期(服务端实时查询,本地不统计)
- 不再本地统计消耗次数:消耗次数改由服务端接口
/v2/zpy/invoice/vas/order/query/service/expiry实时查询真实可用次数与到期。 - 查询方式:
python3 scripts/verify.py quota(走当前 active 凭证);serviceId固定"1276810668"、queryBalance="1"。 - 异常排查:若返回
code=-1(如「当前业务路径未授权」),把接口返回的信息如实转述即可,
不要引导商户去商户中心开通 / 启用任何服务或 scope。 - 每次查验后自动查额度:
verify完成后脚本会再调一次该接口;剩余 ≤3 次时温和提醒「剩余 N 次,用完需联系客服充值」,
用尽时给出数族运营客服联系方式与充值引导话术(充值到账后额度自动恢复、无需重新登录),额度充足则不打扰。
接口/参数/响应/执行细节见 references/usage.md。
安全和边界
- 严禁猜测票面信息(硬约束):票面字段(发票号码
fphm/ 发票代码fpdm/ 开票日期kprq/
金额je/ 价税合计jshj/ 校验码jym)一律用户提供什么就传什么,
严禁推测、臆造、用相似值 / 默认值 / 推断值补齐。用户没给的字段就留空提交(不填),
或先问用户;从附件(PDF / OFD / 图片)读取时必须取票面真实存在的原文值,识别不清就向用户确认。
脚本会在查验前打印本次上送字段,可据此核对未做任何推测填充。 - 严禁自行重试(硬约束):一次调用返回后即结束,不重复调用、不换参数组合再试、
不因失败 / 查无 / 校验不通过而自动调整入参重试。结果如何就如实回报用户(通过 / 查无 /
不通过 / 失败及原因),下一步由用户决定。 - 禁止自行切换 apiKey(硬约束):使用过程中不得自行改用另一把凭证。当前凭证出现
未授权 / 额度不足 / 报错等情况时,必须停下来向用户说明并请其决定:用verify.py keys
列出可用别名让用户点名,征得明确同意后才可用--key <别名>重新执行。
严禁「自动换一把试试」「自动挑一把能用的」「失败后静默改用其它 key」。
尤其禁止以「省额度 / 这把余额低 / 换把余额充足的」为由自行换 key —— 余额低只提示用户,
是否换用由用户决定。反面示例(不得再说/再做):「XX 这把 key 余额偏低,为省它的额度,
我改用余额充足的用户 XXX 复核拿到了完整结果」。正确做法是如实报告当前 key 的余额与消耗,
然后问用户要不要换、换哪把。
(脚本本身不会自动切换:优先级--key>active:true> 仅一把;多把且未指定时直接报错
并列出别名交回用户点名。仅在用户明确指定某把时,才按「选中即启用」回写 active。) - 禁止提示「开通数电」(硬约束):任何情况下都不得主动提示 / 催办 / 引导商户「开通数电」
(含「开通数电(阶段四)」等分阶段开通事项),不得向商户索要电子税务局账号密码、办税人信息
等开通材料。禁止话术示例:「新商户用户X的开通数电(阶段四)还没做,缺电子税务局账号密码和办税人
信息;你什么时候方便给我,我接着跑完」。能力是否可用一律以接口实际返回为准,缺什么如实转述接口
返回即可,不额外引导开通。 - 系统只返回查验平台结果,不做最终法律认定;"未查到"不等于"假票",话术必须表达清楚。
AUTH_KEY只能放在请求头X-AUTH-KEY,绝不写进 URL / Query / 请求体 / 日志。- 群聊中展示税号、身份证号等敏感信息时应按企业安全策略脱敏。
- 用户上传的发票文件是否长期保存,遵守企业留档规则;无明确规则时不默认长期保存。
参考文档
references/usage.md— 接口/参数/响应结构、执行方式、结果判定、额度管理(执行层细节)references/setup.md— AUTH_KEYS 凭证配置、scope 登记、多主体切换