ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

HIS对接上海五期医保接口:黑匣子模式与实战避坑

HIS对接上海五期医保接口:黑匣子模式与实战避坑

简介:面向HIS系统开发者的上海医保五期接口说明文档,用于指导医院信息系统与上海医保中心系统完成数据交互,重点覆盖电子凭证解码、医保项目明细上传、挂号收费、住院结算等核心业务。资源为单个docx文件,压缩包仅34KB,篇幅紧凑但内容体系完整,适合医保接口对接及运维人员快速查阅。文档分为引言、业务分析、接口描述三章,先解释卡类型、账户标志、就诊单元号等关键名词,再梳理读卡、解码、挂号确认、明细上传/撤销、住院结算、对账退款等业务流程,最后给出初始化、ShYbV5_S000读卡、ShYbV5_SE01解码、ShYbV5_SH01挂号等方法说明,并附标准JSON返回报文示例,便于开发者直接对照接口字段进行开发调试。已有1594人学习浏览,是一份实用的一手接口参考资料。

1. 上海五期医保接口:HIS对接医保中心的一座桥

做HIS的同行应该都有这种经历:拿到一份医保接口文档,几十个方法、上百个字段,文档里名词一个比一个绕——就诊单元号、计算申请序号、明细账单号、中心流水号,分不清谁先谁后。这份上海五期医保接口说明,核心就是把前台读卡、挂号收费、住院登记结算这些操作封装成一个个"黑匣子"方法,HIS这边传参数进去,拿JSON出来。它适合正在做或准备做上海医保对接的HIS开发,也适合想搞懂医保结算字段流转的产品和测试人员。文档本身篇幅不大,但如果你只读一遍就上手联调,很容易忽略掉那些能让你对接翻车的细节。本文按"业务是什么→接口怎么用→工程怎么落地→踩过哪些坑→怎么二次开发"这条线拆一遍。

2. 业务模型与技术边界:先搞清楚医保接口在哪一层

2.1 黑匣子模式:HIS不用懂医保规则,但要知道传什么

文档里有一句很关键的话:"本接口程序对于主程序而言,比较简单,类似一个黑匣子,只需按照约定调用该类中的相应方法即可。"这个定位决定了整个对接的技术思路:医保中心系统暴露一堆方法,HIS作为调用方,不参与医保政策计算,只需要按方法签名传入参数、解析返回结果。

但"黑匣子"不等于"不需要懂业务"。实际对接时,HIS至少要理解三件事:卡类型决定调用哪个读卡方法;医保支付办法决定HIS怎么展示自付金额;编号体系决定请求和确认之间怎么关联。这三件事理解不到位,后面全是要返工的坑。

从业务边界看,这个接口覆盖了门诊挂号收费、住院登记结算两大主线,外加明细上传、对账、退款、各类查询。文档列举了16类功能,从读卡到交易查询,基本涵盖了医保患者在院内发生的所有交易场景。HIS侧的调用时机很明确:挂号窗口收费窗口调挂号请求确认,住院处调登记撤销,费用发生时调明细上传,出院时调住院结算。

2.2 三种卡类型的接入差异:芯片卡、磁条卡、电子凭证

文档在名词解释里第一个就讲了卡类型,这是所有接口调用前必须判断的前提条件,我拆解如下:

  • 芯片卡(值为1,保障卡或IC卡):需要插入读卡机,然后调用 ShYbV5_S000 获取病人基本信息。
  • 磁条卡(值为0,共28位):直接在读卡机上刷卡即可得到卡号,不需要调用解码。
  • 电子凭证(值为3):通过支付宝、微信、随申码等渠道获取二维码,然后调用 ShYbV5_SE01 解码,拿到身份证信息和 ectoken 令牌才能继续操作。

这里的差异直接影响HIS前端交互设计。芯片卡和磁条卡的读卡设备不同,电子凭证则需要调起手机端或扫码枪。很多项目在联调初期翻车,就是没有在HIS侧统一屏蔽这些差异,导致每个收费窗口的业务逻辑里散落着各种 if 判断。合格的做法是在HIS内部做一个卡类型适配层,对外暴露统一的"获取病人信息"接口,内部判断调 S000 还是 SE01。

2.3 编号体系:申请序号、就诊单元号、中心流水号的关系

这个接口的编号体系是新手最容易绕晕的地方,我用一条链路把它串起来:

  • 计算申请序号(jssqxh):每次结算请求时中心系统返回的唯一序号,HIS用它查询交易是否成功,也是后续确认时必传的参数。
  • 就诊单元号(jzdyh):由挂号确认或住院登记产生,一个就诊单元对应一段医疗服务过程。
  • 中心流水号(lsh):确认结算时中心系统生成的唯一流水号。
  • 明细账单号(mxzdh):请求明细上传时中心系统生成,一次结算对应一个明细账单。

举例说明:患者挂普通门诊号,HIS先传挂号请求 SH01,中心返回申请序号;再传挂号确认 SH02,带上申请序号,中心返回就诊单元号和中心流水号。之后该患者产生的每笔费用,HIS先通过明细上传 SN01 把费用明细推给中心,中心返回明细账单号;等到收费结算时,HIS把这些编号都带上,中心才能正确组织医保结算。

2.4 医保支付办法:HIS展示层必须知道的规则

虽然医保计算由中心系统完成,但HIS的收费界面要展示"当年账户支付多少、历年账户支付多少、自负段现金多少",这些字段都在挂号请求的返回报文中。文档里列举了几类典型支付办法:

  • 门诊(普通):先用当年账户,再用历年账户,进入自负段,自负累计满后按医院级别一、二、三级分别自负10%、15%、20%。
  • 门诊(大病):统筹封顶线下个人自负8%,封顶线上个人自负20%。
  • 家床:个人自负20%。
  • 住院:起付段以下个人自负,以上自负8%,统筹封顶线上个人自负20%。

这些规则不需要HIS计算,但HIS的结算界面要展示对应字段,比如 curaccountpay(当年账户支付数)、hisaccountpay(历年账户支付数)、zfdxjzfs(自负段现金支付数)等,就是把中心返回的JSON字段渲染到界面上。理解了支付办法,你才知道这些返回字段的业务含义。

3. 核心接口拆解:一次挂号收费从进入到返回的完整链路

3.1 初始化与读卡:Init 与 ShYbV5_S000 的前置关系

文档明确规定:初始化方法需要在每个独立的操作前调用。比如挂号窗口,在进行挂号请求时调用 Init,那么在挂号确认时无需再次调用。这是一个很容易被忽视的状态管理问题,Init 的核心作用是给医保控件赋操作员编码、姓名、交易渠道这些固定字段。

Init 方法的三个参数各有讲究:czybm(操作员编码)和 czyxm(操作员姓名)理论上可以从HIS登录会话中取,但要注意医保中心的操作员编码和HIS的员工号未必一致,通常需要在HIS里做一个对照表;jyqd(交易渠道)传"10"代表线下,传"20"代表线上,诊所和互联网医院的取值不同,这个参数直接关系到医保中心对交易来源的审核。

读卡方法 ShYbV5_S000 比较直观,不需要参数,返回的是一个包含卡号、姓名、性别、身份证号、联系电话、通讯地址、邮政编码、行政区域代码的JSON字符串。返回示例:

{ "kh": "Z59898D78", "xm": "test", "xb": "1", "sfzh": "31000119500417017", "lxdh": "13333333333", "txdz": "上海市某区某街道", "yzbm": "", "xzqh": "310000" }

这里的 kh(卡号)在后续挂号请求中会作为 carddata 参数传入(当卡类型为芯片卡时,carddata 不传卡号而是保持为空,因为中心系统已经通过读卡机获取了卡信息)。这一点逻辑容易绕:芯片卡的读卡动作在S000里完成,挂号请求时 carddata 反而传空。

3.2 电子凭证解码:ShYbV5_SE01 的参数与返回

电子凭证解算是电子医保凭证场景下的前置步骤。HIS前端通过支付宝、微信或随申码获取到一个二维码字符串,这就是 ecQrcode 参数。还有一个 ecQrChannel 参数标识渠道来源,1代表支付宝、2代表微信、3代表随申码。businessType 是用码业务类型,officeId 和 officeName 是医保科室编码和名称。

这个方法返回四个关键字段:username(姓名)、idno(证件号码)、idtype(证件类型)、ecToken(令牌)。ecToken 是整个电子凭证流程中的核心,后续挂号请求时,如果卡类型传的是"3"(电子凭证),那么 carddata 字段要传的就是这个 token,而不是证件号码。

常见做法是:SE01 解码成功后,HIS前端立即把 ecToken 保存在当前会话中,并设定一个合理的失效时间。实际对接中电子凭证的 token 有时效性,患者扫码后在窗口排队时间过长,token 可能已经失效,此时需要重新解码。

3.3 挂号请求与确认:SH01→SH02 的两次握手

挂号请求 ShYbV5_SH01 是整个门诊流程里参数最多的方法之一,我挑几个容易出错的参数重点说明:

  • cardtype:这是请求前必须判断好的,0/1/3分别对应磁条卡、芯片卡、电子凭证。
  • carddata:磁条卡填28位卡号,芯片卡为空,电子凭证填token。
  • zlxmdm(诊疗项目代码):挂号时对应的诊疗项目,不同医院的编码体系不同,需要在HIS字典表里维护映射。
  • personspectag(特殊人员标识):0普通、1离休、2伤残、3干保,这个值一般不存在HIS数据库,而是读卡或解码后由医保中心个性化数据决定,HIS界面上要允许收费员手工修正。
  • totalexpense:交易费用总额,注意是数字格式,不是字符串,小数位要按"分"处理,不能出现 float 精度问题。
  • jmbz(享受社区减免标志):社区医院才用的到,一般医院传0。

返回报文是完整的一套账目结果,curaccountpay、hisaccountpay、zfdxjzfs、tcdzhzfs、tczfs、fjdzhzfs、fjzfs 分别对应历年账户、当年账户、自负段现金、统筹段账户、统筹支付、附加段账户、附加支付。HIS的收费界面就是渲染这些字段。

挂号确认 ShYbV5_SH02 相对简单,只传一个参数 jssqxh(计算申请序号),就是从请求返回的 jssqxh 字段原样传回。返回的核心是 jzdyh(就诊单元号)和 lsh(中心流水号),这两个号码要在后续的收费结算中反复使用,需要持久化保存,不能只存在内存里。

3.4 明细上传:SN01 的前置条件与循环体结构

文档单独提了一个要求:在调用明细上传方法之前,HIS需要提供一个查询病人费用明细的接口,建议使用 webservice,返回JSON格式。这说明明细上传不是一个孤立动作,而是依赖HIS内部先完成费用明细的聚合。

SN01 方法的输入参数有三个:djh(门诊号或住院号)、jzdyh(就诊单元号)、jslxbz(结算类型标志)。jslxbz 的取值有讲究:门诊120、急诊220、家床410、急观510、住院610,门诊大病则是32开头,后一位表示具体大病类型(1化疗、2放疗、3血透、4腹透、6肾移植抗排异、7同位素、8介入、9中医药、A精神病)。这个两位数字组合容易写错,尤其是大病结算不能传120。

费用明细查询接口的输出参数里有一个循环体结构,以 xfmx 为标记,表示这条明细是否还有子明细。父级明细有 xh(序号),子明细通过 xh 关联到上一层明细的序号。子类字段以 zl 结尾,比如 mxxmbmzl(子类明细项目编码)、mxxmdjzl(子类明细项目单价)、bxbzzl(子类报销标志)。这个父子结构对应医保的"材料费带子项"场景,比如一笔手术费下面挂了多个耗材明细。

3.5 请求收费与结算确认:从明细账单号到最终支付

文档的逻辑顺序是:先上传明细,拿到明细账单号(mxzdh),再发起收费请求。SN01 的返回里有 fhm(返回码)、fhxx(返回消息)和 mxzdh(本次明细上传的明细账单号),这三个字段构成一次明细上传的结果。如果 fhm 不是成功码,HIS不应该继续走收费请求。

收费请求之后还有确认环节,这是医保接口的通用模式——请求只试算,确认才真正记账。HIS侧如果中途掉线或超时,请求已经成功但确认未完成,患者就会看到"医生已经结算了但我没付钱"的诡异状态。处理这类异常的办法是保存好请求返回的申请序号,用交易查询方法去中心系统查这笔请求的状态,再决定继续确认还是做撤销。

4. 工程落地:把接口文档转成可维护的调用层

4.1 一个标准的接口调用封装示例

按照文档语义,这些方法最终是通过一个动态库(类似 ShYbV5.dll)暴露给HIS调用的。但在工程落地时,HIS通常不会每个方法直接裸调,而是在业务层做一个封装,统一处理参数组装、返回解析、异常捕获和日志记录。我习惯用C#写一个客户端封装类,大致结构如下:

public class YbV5Client { private readonly IYbV5Native _native; // 实际调用医保中心动态库的接口 private readonly ILogger _logger; public YbV5Client(IYbV5Native native, ILogger logger) { _native = native; _logger = logger; } // 读卡:芯片卡和磁条卡统一走这里,电子凭证走 DecodeQrcode public PatientInfo ReadCard() { var json = _native.ShYbV5_S000(); return JsonSerializer.Deserialize<PatientInfo>(json); } // 电子凭证解码 public EcTokenInfo DecodeQrcode(string qrcode, string channel, string bizType) { var json = _native.ShYbV5_SE01(qrcode, channel, bizType, _officeId, _officeName); return JsonSerializer.Deserialize<EcTokenInfo>(json); } // 挂号请求:组装参数并记录日志 public RegisterRequestResult RegisterRequest(RegisterRequestData data) { var json = _native.ShYbV5_SH01(data.ToRequestString()); _logger.Info($"挂号请求返回: {json}"); var result = JsonSerializer.Deserialize<RegisterRequestResult>(json); if (string.IsNullOrEmpty(result.Jssqxh)) { throw new YbV5Exception("挂号请求成功但未返回计算申请序号"); } return result; } }

封装逻辑说明:ReadCard 对应文档的 ShYbV5_S000,把JSON反序列化成 PatientInfo 对象,HIS业务层只认对象不认字符串;RegisterRequest 在调用原生方法之后,立即检查 jssqxh 是否为空,为空直接抛异常,避免空引用蔓延到上层;所有方法统一走 _logger 记录请求报文和返回报文,便于联调时回溯问题。

参数说明:Native 接口的 ShYbV5_S000 没有参数,但实际部署时它依赖读卡机驱动和医保中心端的环境配置,所以封装的构造函数里接受一个 IYbV5Native 抽象,方便单元测试时用 mock 实现替换真实动态库。

4.2 返回码统一处理与异常边界

文档没有给出完整的返回码字典,但按医保接口的通用约定,返回字符串里的 fhm 字段是判断成功与否的唯一依据。实际对接时,HIS要定义一套统一的状态枚举:

public enum YbV5Status { Success = 0, // 成功 NeedRetry = 1, // 可重试,比如网络超时 FatalError = 2 // 不可重试,需要人工介入 }

判断逻辑:fhm 为成功码且业务字段齐全时返回 Success;fhm 为系统繁忙等临时性错误码时返回 NeedRetry,HIS可自动重试一次;fhm 为参数错误或业务校验失败时返回 FatalError,直接提示收费员并按文档排查参数。注意,在结算场景,重试前要先用交易查询方法确认上一笔请求是否已经生效,否则可能重复结算。这个查证动作很多人会漏掉,等患者发现被扣两次钱就晚了。

4.3 联调环境的模拟与手工验证

真实对接医保中心系统需要申请测试账号和测试卡,但在开发早期,可以自己写一个 mock 服务来模拟中心系统的行为。mock 服务的要点是:能返回固定的JSON,能模拟几种异常分支(比如申请序号为空、余额不足、电子凭证token过期),还能记录HIS请求的参数快照方便断言。

一个简单的 mock 思路是用 ASP.NET Core 写一个 Minimal API,接口路径和真实动态库的方法名对应,返回写死在配置里的JSON。HIS侧通过依赖注入切换真实客户端和mock客户端,接口层不用改一行代码就能在本地把整个挂号收费流程跑通。

手工验证的步骤建议从读卡开始:先用测试卡 S000 拿到卡号,再走挂号请求,确认返回的 jssqxh 非空,然后拿这个 jssqxh 调挂号确认,最终拿到 jzdyh 和 lsh。每一步都核对返回的JSON字段和文档中的类型、长度定义。

5. 实战避坑:对接上海五期接口的常见问题与排查

5.1 芯片卡读卡后重复调用S000导致会话状态过期

现象:收费员先读了芯片卡,又因为界面误操作读了一次,第二次挂号请求报"卡信息无效"或"会话超时"。

原因:S000 读卡成功后,医保中心端生成了本次读卡的会话状态,挂号请求 SH01 会消费这个状态。重复调用 S000 会刷新或覆盖前一次会话,导致第一次读到的卡信息失效。

解决:HIS前端在读卡成功后做状态标记,同一患者同一就诊流程内不允许重复读卡;如果确实需要重新读卡,后端统一先做一次会话重置操作,再走新的读卡流程。

5.2 挂号请求返回的申请序号丢失导致确认失败

现象:挂号请求成功,但确认时提示"申请序号不存在"或"该序号已使用"。

原因:jssqxh 是一个36位以内的长字符串,某些数据库字段定义成了 int,超长截断或类型转换失败;更常见的是HIS前端把 jssqxh 放在页面缓存里,页面刷新或收费员切换窗口后丢失。

解决:HIS后端在请求成功后立即持久化 jssqxh,和门诊号/就诊单元号绑定存储;确认时从数据库读取,不依赖前端传参。从那以后我每次设计医保接口调用,都把"请求成功的结果必须落库"作为强制约束,前端只传业务主键,不传中心返回的序号。

5.3 明细金额出现分位精度错误

现象:明细上传后,中心系统返回"金额不一致"或"明细金额与结算金额不匹配"。

原因:文档里大量字段标注为"数字格式",但没有明确精度。常见做法是金额以"分"为单位上传整数,但HIS内部用 float 或 double 计算后转字符串,出现浮点尾差。比如 0.1 + 0.2 等于 0.30000000000000004,转成分就是 30 分和 30.000000000000004 分的差别。

解决:所有金额字段统一用 decimal 类型,并且在HIS内部约定以"分"为单位的整数传递。上传前做一个金额汇总校验,把明细合计和请求收费的 totalexpense 比对,误差超过1分直接拦截,不放行到中心系统。

5.4 明细上传成功但收费请求报"无明细账单号"

现象:SN01 返回成功,但紧接着调收费请求时报"未找到明细账单"。

原因:SN01 的返回里有 fhm、fhxx 和 mxzdh 三个字段,HIS只判断了 fhm 成功就继续走流程,但没有保存 mxzdh;或者收费请求参数里要求传明细账单号但HIS传的是空的。

解决:SN01 返回后除了判断返回码,还要校验 mxzdh 不为空,并把这个明细账单号持久化,收费请求时原样透传。如果 mxzdh 为空,说明明细上传实际上没有成功,需要重新上传。

5.5 电子凭证 token 失效导致的挂号失败

现象:患者用电子凭证扫码,HIS已经调 SE01 解码成功并显示了患者姓名,但挂号请求报"令牌无效"。

原因:ecToken 有时效,解码动作和挂号动作之间间隔太久,或者中间又调了一次解码导致旧 token 被覆盖。

解决:SE01 解码成功后立即进入挂号请求,界面上减少不必要的停留;如果流程需要跨页面,HIS把 token 保存在服务端会话并记录解码时间,超过时限自动重新解码并友好提示患者再次扫码。

6. 二次开发:从一份文档到一套可维护的医保对接模块

6.1 建立字段映射表而不是写死结构体

文档里的JSON字段是下划线命名(比如 curaccountpay),而HIS内部的实体类普遍是驼峰命名(CurAccountPay)。最省事的做法是反序列化时直接用下划线命名建类,但这样会把支付宝风格的字段名散落到整个业务代码里,后续HIS内部标准字段变更时牵一发动全身。

我建议在对接层维护一张字段映射表,把文档中的字段映射到HIS内部统一的医保结算模型。映射表可以用配置文件维护,也可以做成数据库字典表,这样医保中心调整字段名时只需要改映射配置,不用动业务代码。

6.2 构建本地模拟器跑通全流程

二次开发最痛苦的是联调环境不可用。我习惯在对接层之外写一个模拟器工程,用内存数据模拟中心系统的行为:S000 返回一张测试卡,SH01 根据参数返回对应的支付段金额,SH02 返回就诊单元号,SN01 返回明细账单号。模拟器对HIS的调用方是透明的,切换只需要改一个配置文件。

在模拟器里额外做一件事:记录每一次请求的完整参数快照和返回报文,输出到本地日志。联调遇到问题时,直接看快照就能定位是HIS组装参数的问题还是中心系统判断的问题,不用再靠收费窗口的截图来复盘。

6.3 验证方法的顺序与自动化回归

每次改动对接层代码后,我习惯按固定顺序走一遍回归:初始化→读卡→挂号请求→挂号确认→明细上传→收费请求→收费确认→对账→撤销。这九个动作覆盖了文档里90%的接口方法,把回归脚本固化下来,用模拟器跑一遍能发现大部分低级错误。

结合模拟器日志,对比每次改版前后的报文差异,重点盯三个地方:卡类型与 carddata 的传递逻辑是否被破坏、jssqxh 和 mxzdh 是否在流程中被正确持久化、金额字段的精度是否保持统一。

这套对接模块只要按"字段映射配置化、流程状态持久化、模拟器可回归"三个原则来做,后续医保中心升级接口规范时,你只需要改映射和个别方法签名,不用重写业务逻辑。现在每换一个医保项目,我都是先花半天时间搭好映射表和模拟器,把黑匣子变成看得见的白盒,再开始碰业务代码。希望这份拆解能帮你在对接路上少走几步弯路。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进