如果您的应用现在会让用户拍摄护照或身份证照片,您大概听说过欧盟数字身份钱包(EU Digital Identity Wallet)会在“2026 年底”到来。这个日期是真实的,而且是确定的:2026 年 12 月 24 日。但这是成员国的期限,不是您的期限,它也不会让证件扫描退出历史舞台。

本文做三件事。第一,给出这个日期的出处,并附上计算过程。第二,根据法规中的属性表,列出钱包实际交出哪些数据。第三,勾勒一种设计:用户有钱包时接受钱包,没有时回退到证件扫描。本文由护照与身份证 OCR API doc.cheap 撰写,阅读产品相关部分时请记住这一点。doc.cheap 读取的是证件图片,不读取钱包。

2026 年 12 月 24 日从何而来

这项义务是 Reg. (EU) No 910/2014 第 5a 条第 1 款,由 eIDAS 修订法 Reg. (EU) 2024/1183 插入。该款规定,每个成员国“应在本条第 23 款和第 5c 条第 6 款所述实施法生效之日起 24 个月内,提供至少一个欧洲数字身份钱包”。

所以计时并不是从 eIDAS 修订法本身开始的,而是从欧盟委员会的实施法开始。第 5a 条第 23 款所述的实施法是欧盟委员会于 2024 年 11 月 28 日通过的四部实施法,其中包括关于钱包完整性和核心功能的 Implementing Reg. (EU) 2024/2979。它于 2024 年 12 月 4 日在官方公报(OJ L, 2024/2979)上公布。其第 15 条规定,它“应在《欧盟官方公报》公布之日后的第二十日生效”。

计算如下:

步骤 日期
在官方公报上公布 2024 年 12 月 4 日
公布后第 1 日 2024 年 12 月 5 日
公布后第 20 日:生效 2024 年 12 月 24 日
加 24 个月(第 5a 条第 1 款):钱包到期 2026 年 12 月 24 日
加 36 个月(第 5f 条第 2 款):部分私营依赖方必须接受钱包 2027 年 12 月 24 日

“公布之日后的第二十日”从公布次日起算,所以 12 月 4 日加 20 天落在 24 日,而不是 23 日。表中第二个日期也由同一部实施法推算,而它才是大多数产品团队真正关心的日期(下文详述)。

钱包交出什么

钱包不会发送护照照片,而是出示经过签名的数据。自然人的数据集规定在 Implementing Reg. (EU) 2024/2977 的附件中,称为“个人身份数据”(person identification data,PID)。附件规定 PID 以两种格式签发:ISO/IEC 18013-5:2021 和 W3C “Verifiable Credentials Data Model 1.1”。

以下是各项属性,以及正文对每项属性规定的出现要求(附件表 1、表 2 和表 5,以 2024 年 12 月 4 日公布的版本为准)。最后一列是 doc.cheap 扫描响应中最接近的字段,您可以看到两个世界在哪里对得上、在哪里对不上。

PID 属性 要求 证件扫描中最接近的字段
family_name 必填 holder.surname
given_name 必填 holder.given_names
birth_date 必填 holder.birth_date(ISO 8601)
birth_place 必填 证件上印有时,fields[] 中的 birth_place 条目
nationality 必填(alpha-2,一个或多个) holder.nationality(alpha-3,一个)
resident_address、resident_country、resident_state、resident_city、resident_postal_code、resident_street、resident_house_number 可选 护照资料页上没有
personal_administrative_number 可选 不是同一样东西;fields[] 中可能有证件上印的 personal_number
portrait 可选 images.main_photo
family_name_birth、given_name_birth 可选 没有整理好的字段
sex 可选(代码 0、1、2、3、4、5、6、9) holder.sex(M、F、X)
email_address、mobile_phone_number 可选 证件上没有
expiry_date(元数据) 必填 document.expiry_date,但那是证件的有效期,不是 PID 的
issuing_authority(元数据) 必填 印有时,fields[] 中的 authority 条目
issuing_country(元数据) 必填(alpha-2) document.issuing_state(alpha-3)
document_number(元数据) 可选 不是同一样东西:PID 的编号由 PID 提供方分配,document.number 是护照的号码
issuing_jurisdiction、location_status(元数据) 可选 无

编写映射代码时,这张表里有三点很重要。

  • 国家代码不同。 PID 使用 ISO 3166-1 alpha-2(DE)。护照和机读区(MRZ)使用 alpha-3(DEU)。内部只保留一种形式,在边界处转换。
  • “必填”不等于“总是已知”。 在表 1 下方,正文补充道:“如果某人的某项属性值未知,或因其他原因无法作为个人身份数据集的一部分签发,成员国应改用适合该情况的属性值。”请预期会收到占位值,而不是缺失的键。
  • 关于本人的必填属性只有五项。 地址、人像照片、性别和出生时姓名都是可选的。如果您的流程需要其中某一项,对某些用户来说,钱包可能根本不包含它。

谁仍会带着证件来

期限要求每个成员国提供钱包,并不要求任何人使用钱包。第 5a 条第 15 款说得很直白:“欧洲数字身份钱包的使用应是自愿的。”接着又说:“通过其他现有的识别和认证手段访问公共和私营服务,应仍然可行。”

因此,2026 年 12 月 24 日之后,您仍会遇到:

  • 来自欧盟以外的旅客和客户。 序言(recitals)把钱包与“欧盟公民、欧盟居民或法人的法定身份”联系在一起。持其他地区护照的访客没有欧盟钱包可以出示。
  • 没有安装钱包的人,或者无法安装、或者不愿安装的人。正文保护这种选择。
  • 需要证件本身的流程。 有些流程需要的是实体证件的图片、证件号码或机读区,而不是关于本人的证明。钱包的 PID 带有自己的 document_number,由 PID 提供方分配,并不是护照号码。
  • 某个国家的钱包正式上线之前的这段时间。 2026 年 12 月 24 日是法定期限。各国钱包何时真正送到用户手中是另一个问题,对任何一个国家来说,唯一可靠的答案是该国自己的公告或欧盟委员会的公告。

两者兼收的设计

在上述所有情况下都站得住的结构很简单:用户有钱包时请求钱包出示,没有时回退到证件扫描。两条路径最终汇入同一条内部记录。

user starts verification
        |
        v
  offers a wallet? --yes--> wallet presentation --> verify signature --> map PID
        |                                                               |
        no                                                              v
        |                                                      internal identity
        v                                                          record
  document photo --> POST /v1/scans --> map scan fields ----------------^

几条规则能让两条路径互相替换:

  1. 把两者映射到同一条记录,使用您自己的字段名。把上面的表当作映射表。
  2. 保留来源。 记录这条数据来自钱包还是扫描。两者承载的证据不同,审核人员会想知道是哪一种。
  3. 把 null 视为如实的值。 在 doc.cheap 的响应中,所有键始终存在,未知值为 null。钱包则可能改为发送占位值。请把两者规范化为同一种约定。
  4. 只存储流程所需的最少数据。 扫描可以设置为 API 端不写下任何内容(见下文)。

下面是用原生 fetch 实现的后备调用。公开沙箱(sandbox)密钥 sk_sandbox_public 印在文档中,无需注册;它按客户端地址限流。

import { readFileSync } from "node:fs";
import { randomUUID } from "node:crypto";

async function scanDocument(path, apiKey = "sk_sandbox_public") {
  const response = await fetch("https://api.doc.cheap/v1/scans", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": randomUUID(), // 重试会返回第一次的结果
    },
    body: JSON.stringify({
      image: readFileSync(path).toString("base64"),
      options: { retain_hours: 0, return_portrait: false }, // 不存储任何内容
    }),
  });
  if (!response.ok) throw new Error(`scan failed: HTTP ${response.status}`);
  return response.json();
}

// 把扫描结果映射到钱包出示时会填充的同一条记录。
function fromScan(scan) {
  if (scan.meta.status !== "recognized") return null;
  const field = (name) => scan.fields.find((f) => f.id === `${name}@0`)?.value ?? null;
  return {
    source: "document_scan",
    family_name: scan.holder?.surname ?? null,
    given_name: scan.holder?.given_names ?? null,
    birth_date: scan.holder?.birth_date ?? null,
    birth_place: field("birth_place"),
    nationality_alpha3: scan.holder?.nationality ?? null,
    issuing_country_alpha3: scan.document?.issuing_state ?? null,
    document_number: scan.document?.number ?? null,
    mrz_status: scan.mrz.status, // "passed"、"failed" 或 "absent"
  };
}

meta.status 是五个字符串之一:recognized、no_document_found、unreadable、unsupported_document 或 rejected。只有第一种应当填充记录;其余几种应引导用户重新拍照。使用付费密钥时,只有扫描计费时才会扣减余额。

我们于 2026 年 9 月 24 日 21:17 UTC,用产品自己的测试证件(一本护照)对 sk_sandbox_public 执行了一次这样的调用。响应返回 HTTP 200。下面所有证件值均已遮盖,fields、images 和 mrz.lines 已做删减:

{
  "meta": {
    "schema_version": "1.0",
    "id": "<SCAN_ID>",
    "status": "recognized",
    "billed": true,
    "confidence": "medium",
    "timing": { "upload_ms": 255, "processing_ms": 409, "total_ms": 678 },
    "created_at": "<RUN_TIMESTAMP>",
    "reference": null
  },
  "document": {
    "kind": "passport", "country": "<ISO3>", "country_name": "<COUNTRY>",
    "issuing_state": "<ISO3>", "number": "<DOCUMENT_NUMBER>", "series": null,
    "issue_date": "<DATE>", "expiry_date": "<DATE>", "is_expired": false, "days_remaining": "<N>"
  },
  "holder": {
    "given_names": "<GIVEN_NAMES>", "surname": "<SURNAME>", "full_name": "<FULL_NAME>",
    "birth_date": "<DATE>", "sex": "<SEX>", "nationality": "<ISO3>"
  },
  "mrz": { "status": "passed", "reason": null, "lines": ["<LINE_1>", "<LINE_2>"], "text": "<MRZ>" },
  "quality": { "overall": "pass" },
  "authenticity": { "overall": "not_checked", "checks": [] }
}

这里的 billed: true 表示这次扫描属于计费扫描,已计入沙箱的免费额度;沙箱密钥本身永远不会被扣费。那次运行的 fields 数组中包含 birth_place、authority 和 personal_number 条目,上表为这些 PID 属性所指向的正是它们。护照资料页上的 MRZ 采用 TD3 版式,共两行;TD1、TD2 和 TD3 格式页面把几种版式并排展示。

请注意 authenticity.overall: "not_checked"。这样的扫描属于识别:它读取印刷的内容,并校验 MRZ 的校验位。它不是伪造检测,也不具备与签名钱包出示相同的保证。如果您的流程在扫描路径上需要更高的保证等级,这必须来自流程中的其他环节。如果您正在为后备方案挑选服务商,我们的护照 OCR API 对比列出了各种选项,包括那些不止做识别的方案。

接下来要关注什么

  • 2026 年 12 月 24 日 – 钱包到期。 第 5a 条第 1 款,Reg. (EU) 2024/1183,按上文所示从实施法生效之日起算。
  • 2027 年 12 月 24 日 – 私营依赖方。 第 5f 条第 2 款规定,依法律或合同必须在在线识别中使用强用户认证的私营依赖方,“应在第 5a 条第 23 款和第 5c 条第 6 款所述实施法生效之日起最迟 36 个月内,且仅在用户自愿请求时,也接受欧洲数字身份钱包”。正文列出了交通、能源、银行、金融服务、社会保障、卫生、饮用水、邮政服务、数字基础设施、教育和电信,并排除了微型企业和小型企业。
  • 超大型在线平台。 第 5f 条第 3 款要求它们在用户认证中接受钱包,同样仅在用户自愿请求时,且仅限所需的最少数据。
  • 实施法的修改。 Implementing Reg. (EU) 2024/2979 的序言第 4 条指出,欧盟委员会“应在必要时对其进行审查和更新”。在确定映射之前,请先核对 2024/2977 中的属性表。

实际的结论是:当用户所在国家推出钱包时再构建钱包路径,保留证件路径,并从第一天起就把两者映射到同一条记录。扫描这一侧的完整内容,请参阅护照与身份证 OCR API 文档。

本文是工程层面的摘要,不构成法律建议。