# 没有意大利税号的会员

> 识别一个人，可以通过税号，也可以通过出生日期、出生地和出生国。在国外出生的人要发送什么，不一致的税号如何确认，入场时要显示什么。

URL: https://developers.vimine.network/zh-hans/guides/members-without-cf/

加入协会**并不需要意大利税号**（codice fiscale）。需要的是当事人的身份已被识别；在 Vimine 上，有两种方式：

- 通过**税号**，或者
- 通过**出生信息**：`dob` + `birthplace` + `birth_country`（ISO 3166-1 alpha-2）。

两者不能都没有。本指南的其余部分逐个字段说明，对于为他人办理入会的集成——交换生、刚到意大利的志愿者、住在国外的会员——有哪些变化。

## 识别一个人

`POST /v1/enroll` 和 `POST /v1/signup` 接受相同的字段：

| 字段 | |
|---|---|
| `codicefiscale` | 可选。提供时会进行校验，并与姓名、出生日期和出生地比对 |
| `dob` | 出生日期，`YYYY-MM-DD` |
| `birthplace` | 在意大利出生的人填市镇（comune）；在国外出生的人填城市，自由文本 |
| `birth_country` | 出生国，两个字母（`IT`、`FR`、`SN`…） |
| `gender` | 仅用于统计：**不再**与税号比对 |

```js
// 没有税号：出生日期、出生地和出生国
await vimineServer.enroll({
    email: 'marie.dupont@example.org',
    first_name: 'Marie',
    last_name: 'Dupont',
    dob: '1985-03-10',
    birthplace: 'Lyon',
    birth_country: 'FR',
    accept_statuto: { version: statuto.version, sha256: statuto.sha256 }
});
```

在 `/v1/enroll` 上，没有税号时，每缺少一项出生信息，就返回 `422 validation_failed`，并在 `errors[]` 中有一条记录：

```json
{ "code": "validation_failed", "errors": [{ "field": "birth_country", "code": "required" }] }
```

`/v1/signup` 只创建账户：在这里出生信息仍是可选的，等当事人申请会员资格时才需要。

## 在国外出生：发送 `birth_country`

在意大利出生的人，税号中包含出生市镇；在国外出生的人，税号中**只包含国家**。因此，对后者的比对看的是 `birth_country`，并把 `birthplace`（城市）当作自由文本。发送 `birth_country`，正是让持有有效税号、在国外出生的人通过校验的关键：如果不发送，“Lyon”会被当作意大利市镇去查找，税号就会显得不一致。（以前把国家的意大利语名称当作出生地发送的表单——`birthplace: 'Francia'`——仍然可以通过；但更推荐使用 `birth_country`。）

公开校验接口也是如此，现在它还会说明税号中包含什么：

```js
const r = await publicApi().validateFiscalCode({
    code: 'DPNMRA85C50Z110U', first_name: 'Marie', last_name: 'Dupont', date: '1985-03-10', place: 'Lyon', country: 'FR'
});
// { valid: true, error: null, mismatches: [], hints: [], birth_date: '1985-03-10', birth_country: 'FR' }
```

`birth_date` 和 `birth_country` 是从税号本身读取的：您可以用它们预填表单。如果您发送了 `gender`，这个端点仍会比对它；入会和个人资料则不会。

## 不一致的税号与确认

输错的税号（结构或校验码有误）返回 `invalid_codicefiscale`：必须更正，永远不能确认。格式正确但与所填信息**不一致**的税号，则返回 `422`，并带有一条信息更丰富的记录：

```json
{
  "code": "validation_failed",
  "errors": [{
    "field": "codicefiscale",
    "code": "codicefiscale_mismatch",
    "mismatches": ["place"],
    "hints": [],
    "confirmable": true
  }]
}
```

- `mismatches`——哪些地方不一致：`lastName`、`firstName`、`date`、`place`；
- `hints`——当姓和名互换后税号能对上时，为 `names_swapped`：通常提示对方互换即可；
- `confirmable`——当事人能否**确认**这就是其医保卡（tessera sanitaria）上的税号。这种情况确实会发生：已撤销的市镇、音译、以另一种顺序登记的姓名。

当 `confirmable` 为 `true` 时，请询问当事人，并带上 `codicefiscale_confirmed: true` 重复请求。当姓、名**和**出生日期全都不一致时，就再也没有什么能把这个税号和这个人联系起来：`confirmable` 为 `false`，唯一的办法是更正。

```js
import { VimineError } from '@vimine/sdk/server';

try {
    await vimineServer.enroll(input);
} catch (err) {
    const entry = err instanceof VimineError && err.errors?.find((e) => e.code === 'codicefiscale_mismatch');
    if (!entry) throw err;
    if (entry.confirmable && (await askThePerson(entry.mismatches, entry.hints))) {
        await vimineServer.enroll({ ...input, codicefiscale_confirmed: true });
    }
}
```

不要自作主张发送 `codicefiscale_confirmed`：这是当事人的声明，并会作为声明被记录。

## 之后再补充税号

入会时没有税号的人，在取得税号后可以在自己的个人资料中补充：

```js
const me = await vimine.me.update({ codicefiscale: 'DPNMRA85C50Z110U' });
me.codicefiscale_check;   // 'matched'
```

`PATCH /v1/me` 接受 `birth_country` 和只写字段 `codicefiscale_confirmed`；`codicefiscale` 也可以重新设为 `null`。`GET /v1/me` 说明当事人是如何被识别的：

| `codicefiscale_check` | |
|---|---|
| `matched` | 税号与当事人的出生信息一致 |
| `mismatch_confirmed` | 税号不一致，但已由当事人确认 |
| `absent` | 没有税号：通过出生日期、出生地和出生国识别 |
| `null` | 尚未识别：不能申请会员资格 |

`matched` 的意思是**一致**，而不是已经过税务登记机关核实：税号始终是当事人申报的信息。

## `identification_required`

账户可以存在但尚未识别身份——例如只为登录而注册的人。当这样的人申请会员资格时，API 会明确指出，并列出缺少什么：

- **`POST /v1/me/membership`** → `422 identification_required`，`errors[]` 为缺少的出生信息（`{ field: 'birthplace', code: 'required' }`…）。请引导当事人通过 `PATCH /v1/me`（权限 `profile:write`）完善个人资料，然后重复请求。
- **`POST /v1/enroll/confirm`**——已有账户的所有者收到的“确认您的申请”链接 → `409 identification_required`，`errors[]` 相同。请在页面上询问出生信息，并带上 `identification` 再次提交令牌：

```js
await publicApi().confirmEnrollment(token, { dob: '1985-03-10', birthplace: 'Lyon', birth_country: 'FR' });
// POST /v1/enroll/confirm  { token, identification: { dob, birthplace, birth_country, codicefiscale?, codicefiscale_confirmed? } }
```

出生信息放在**请求体中传送，绝不放在链接中**：链接里从来没有任何个人信息。`identification` 只补全个人资料中的空字段，不会覆盖已有内容；如果校验不通过，响应就是上文那样的 `422 validation_failed`。

入会审批不变：`pending_reasons` 对所有人都取决于协会的入会模式（参见[入会审批与生效](https://developers.vimine.network/zh-hans/guides/admission/)）。

## 入场时：身份核对

与出生信息一致的税号可以据此核对；仅有出生信息，或由当事人确认的税号，则无法核对。对于这些会员，协会会在当事人第一次到场时查看**一次**证件。集成扫码器的开发者会在 `POST /v1/checkins`（权限 `checkins:write`）针对会员卡的响应中看到它：

```js
const r = await vimineServer.checkins.create('vmt_…');
// { kind: 'card', verdict: 'valid', …, identity_check: 'pending' }
if (r.identity_check === 'pending') show('首次入场：请查验证件');
```

| `identity_check` | |
|---|---|
| `pending` | 该会员没有可核实的税号，协会中也还没有人查看过其证件：请把它**显示给**入口值守人员 |
| `verified` | 协会中已有人查看过证件 |
| `flagged` | 有人查看过证件，但与信息不符：由协会秘书处接手处理 |
| `null` | 无需核对 |

这只是一条**提示**，不属于 `verdict`，也**不限制任何操作**：带有 `identity_check: 'pending'` 的 `valid` 会员卡照样可以入场。核对结果在协会的管理后台中记录，而不是通过 API。公开核验接口 `GET /v1/verify/{card_token}` 从不携带它：从外部核验会员卡的人只能看到状态和到期日。

## 地址

有些协会会在会员登记册中保存会员的地址，有些则不会。这由协会决定，并且对**所有会员都适用，或对所有会员都不适用**；公开资料中会声明：

```
GET https://api.vimine.network/v1/aps/{slug}
→ { "aps": { …, "member_address": "optional" } }
```

| `member_address` | |
|---|---|
| `none` | 不要询问：即使发送了 `address` 也不会保存 |
| `optional` | 询问，但不强制 |
| `required` | 没有 `address` 时，入会返回 `422`，并带有 `{ field: 'address', code: 'required' }` |

```js
await vimineServer.enroll({
    …,
    address: { kind: 'residence', line: 'Via Roma 1', postal_code: '20121', city: 'Milano', province: 'MI', country: 'IT' }
});
```

`address` 中的 `line` 和 `city` 为必填；`kind` 为 `residence`（默认，户籍地）或 `domicile`（住所地，与户籍地不同时填写）；省略 `country` 时为 `IT`；`province` 仅适用于意大利地址。`/v1/signup` 没有 `address`：地址属于会员资格，而不属于账户。

## 协会不能强制要求税号

不存在“税号必填”这样的设置。协会不能要求每位申请人都提供税号：没有税号的人，在任何地方都通过出生信息来识别。如果您的表单把税号设为必填，那是您的表单的选择——它会把协会本可以接纳的人挡在门外。

## 变更内容（2026-09-19 版）

全部为新增：**始终发送税号的表单无需任何改动即可继续使用**。现有集成可能会注意到：

- `POST /v1/enroll` 中的 `codicefiscale` 不再是必填项；没有税号时，需要 `dob`、`birthplace` 和 `birth_country`（每缺少一项返回 `422` `required`）；
- 新的可选字段 `birth_country`、`codicefiscale_confirmed`（enroll、signup、`PATCH /v1/me`）和 `address`（enroll）；
- `gender` 不再与税号比对：不会再因为性别单独返回 `422`；
- 不一致的税号仍返回 `codicefiscale_mismatch`，现在在 `mismatches` 之外还带有 `hints` 和 `confirmable`；格式错误的税号仍是 `invalid_codicefiscale`；
- `GET`/`PATCH /v1/me` 现在有声明的 schema（`Me`），包含 `birth_country` 和 `codicefiscale_check`；
- 新错误 `identification_required`：在 `POST /v1/me/membership` 上为 `422`，在 `POST /v1/enroll/confirm` 上为 `409`（后者在请求体中接受 `identification`）；
- `POST /v1/validate/fiscal-code` 接受 `country`，并且还返回 `hints`、`birth_date`、`birth_country`；`error` 为 `invalid`、`mismatch` 或 `null`；
- `POST /v1/checkins` 在针对会员卡的响应中增加了 `identity_check`；`GET /v1/verify/{card_token}` 保持不变；
- 公开资料增加了 `aps.member_address`。
