# 伺服器整合

> 用於協會自有系統的機密用戶端、client_credentials、會員卡狀態、secret 輪換。

URL: https://developers.vimine.network/zh-hant/guides/server/

對於**協會自己的**系統——管理軟體、閘門、CMS 外掛——使用帶有 `client_id` + `client_secret` 的**機密**用戶端。secret 只顯示一次，絕不能出現在瀏覽器中。

## 驗證

```js title="Node"
import { VimineServer } from '@vimine/sdk/server';

const vimineServer = new VimineServer({
    clientId: 'vmc_…',
    clientSecret: process.env.VIMINE_SECRET,
    scopes: ['memberships:read']
});
const token = await vimineServer.token();      // client_credentials — 1 小時有效的權杖，自動續期
```

不使用 SDK 時：`POST /v1/oauth/token`，帶上 `grant_type=client_credentials` 和 HTTP Basic `client_id:client_secret`（或在請求本文中傳 `client_id`/`client_secret`）：

```sh
curl -u "vmc_…:vms_…" -d "grant_type=client_credentials&scope=memberships:read" \
     https://api.vimine.network/v1/oauth/token
```

在讀取端點（`/v1/memberships/{id}/status`、`/v1/memberships`）上，也可以**直接**使用 HTTP Basic，無需先取得權杖，方便硬體和低程式碼平臺——但絕不能在瀏覽器中這樣做。

## 會員卡狀態

```js
const s = await vimineServer.memberships.status('membership-uuid');
// { id, status: 'active', verdict: 'valid', valid_until: '2027-…', membership_type: { cod: 'ORD' } }
```

不含任何個人資料：只有狀態和有效期。`id` 是會員在自己的會員卡上看到的 id（透過“使用 Vimine 登入”獲得的 `membership.id`），或您的系統在登記該會員卡時收到的 id。

如需核驗**入場時出示的**會員卡，請改用 QR Code 中的權杖呼叫 [`/v1/verify/{card_token}`](https://developers.vimine.network/zh-hant/guides/embed-and-verify/)：它完全不需要驗證。

## 從您的系統為會員辦理入會

擁有 `identity:signup` 和 `enroll:write` 後，您的管理系統可以建立帳戶並提交入會申請，**無需當事人經過 Vimine**：當事人會透過電子郵件收到連結，用於確認電子郵件地址並設定密碼。

```js
// 僅建立帳戶（此人尚未入會）
await vimineServer.signup({ email: 'mario.rossi@example.org', first_name: 'Mario', last_name: 'Rossi' });

// 帳戶 + 待處理的入會申請，由秘書處在 Vimine 管理後臺中批准
const { statuto, membership_types } = await vimineServer.public.association('your-slug');
await vimineServer.enroll({
    email: 'mario.rossi@example.org',
    first_name: 'Mario',
    last_name: 'Rossi',
    codicefiscale: 'RSSMRA80A01H501U',      // 可選：沒有稅號時，需要 dob + birthplace + birth_country
    dob: '1980-01-01', birthplace: 'Roma', birth_country: 'IT',
    gender: 'M',                              // 僅用於統計：不與稅號比對
    membership_type: membership_types[0].uid, // 只有一種類型時可省略
    accept_statuto: { version: statuto.version, sha256: statuto.sha256 },
    privacy_notice: privacy_notice && { version: privacy_notice.version, sha256: privacy_notice.sha256 } // 協會發布了隱私權聲明時必填
});
```

無論該地址背後是什麼情況，回應**始終**是 `202 { status: 'accepted' }`：您無法得知某個電子郵件地址是否已有 Vimine 帳戶（只有信箱的所有者會透過郵件得知）。具體來說：

- **新地址** → 建立帳戶；使用 `enroll` 時，還會建立一份*待處理*的入會申請；傳送歡迎郵件，附帶電子郵件地址確認；
- **已註冊的地址** → 使用 `enroll` 時不寫入任何內容：信箱所有者會收到一個“確認您向 *<協會>* 提交的申請”的連結，有效期 24 小時，只有在他使用該連結後才會建立申請。如果他已經是會員，則什麼也不會發生；
- `422` 錯誤只與*您的*請求資料有關（帶 `errors[].field` 的 `validation_failed`、`statuto_outdated`、`invalid_type`）；協會不接受申請時傳回 `403 api_disabled`；在[內測](https://developers.vimine.network/zh-hant/guides/scopes-and-errors/#內測)期間帳戶只能憑邀請建立，此時傳回 `403 not_available`。

識別一個人，要麼依靠**稅號**，要麼依靠出生日期、出生地和出生國（`dob`、`birthplace`、`birth_country`）——兩者不能都沒有：沒有稅號時，每缺少一項出生資訊，`errors[]` 中就有一條 `required`。對於在國外出生的人，請務必傳送 `birth_country`：他們的稅號中只包含國家。稅號與出生資訊不一致時，傳回 `422`，其中一條 `codicefiscale_mismatch` 帶有 `mismatches`、`hints` 和 `confirmable`：當 `confirmable` 為 `true` 時，當事人可以確認該稅號，您再帶上 `codicefiscale_confirmed: true` 重複呼叫。如果協會登記會員的地址（公開資料中的 `aps.member_address`），請加上 `address`。如果協會收集監護人（行使父母責任者）的資訊（`aps.collects_guardian_data`），且根據 `dob` 此人為未成年人，則 `guardian` 為必填——`first_name`、`last_name`、`relation`，可選 `codicefiscale`、`email`、`phone`——否則傳回 `422`，並帶有 `guardian: required`。完整流程見[沒有義大利稅號的會員](https://developers.vimine.network/zh-hant/guides/members-without-cf/)。

`accept_statuto` 必須指明**現行**章程：請在您的網站上向當事人展示章程（可在 [`association(slug)`](https://developers.vimine.network/zh-hant/sdk/public/) 中找到；文字和文件在 `GET /v1/aps/{slug}/statuto`），並傳入其接受的版本。未登記可讀章程的協會會傳回 `accepting_requests: false`，申請則傳回 `403 not_available`。協會給會員的**隱私權聲明**也是如此（`association(slug)` 中的 `privacy_notice`，文字在 `GET /v1/aps/{slug}/privacy-notice`）：協會發布了隱私權聲明時，申請必須指明當事人閱讀的版本，否則傳回 `422`（缺少 `privacy_notice` 或 `privacy_notice_outdated`）；閱讀確認以當事人的名義記錄，並註明您的用戶端。規則相同的可選項：`accept_terms` 和 `acknowledge_privacy`（帳戶使用條款和平臺的隱私權聲明，`GET /v1/legal/{kind}`），在當事人已於您的網站上接受時傳入。這些呼叫是冪等的：SDK 會為每次呼叫產生一個 `Idempotency-Key`，您也可以傳入自己的鍵（`{ idempotencyKey: 'order-42' }`），以便在逾時後安全地重試。郵件有傳送配額（每個地址每天 3 封），沙箱中從不傳送郵件。

## 入場登記

擁有 `checkins:write` 後，入口處的裝置透過傳送**從會員卡 QR Code 中讀取的權杖**（而不是某個識別碼）來記錄會員的通行：回應只有判定結果，不含任何個人資料。可以直接使用 HTTP Basic，方便硬體和低程式碼平臺。

```js
const r = await vimineServer.checkins.create('vmt_…');   // { verdict: 'valid' | 'expired' | 'not_yet_valid' | 'statuto_required' | 'inactive', status, membership_id, valid_until, identity_check, checked_in_at }
```

```sh
curl -u "vmc_…:vms_…" -H "Content-Type: application/json" -d '{"token":"vmt_…"}' https://api.vimine.network/v1/checkins
```

請求本文為 `{ "token": "vmt_…" }`——從 QR Code 中讀取的權杖，可以來自會員卡，也可以來自活動報名；僅對會員卡而言，`card` 也可作為別名使用。

格式錯誤或偽造的權杖傳回 `422 invalid_card`；其他協會的會員卡則是“找不到”。

`identity_check: 'pending'` 是給入口值班人員的提示：該會員沒有可核實的稅號，協會中也還沒有人檢視過其證件。請把它顯示出來；它不屬於 `verdict`，也不限制任何操作（參見[身份核對](https://developers.vimine.network/zh-hant/guides/members-without-cf/#入場時身份核對)）。

## 會員卡清單（Pro 和 Network 方案）

`vimineServer.memberships.list({ status?, updated_since?, limit?, cursor? })` 分頁傳回（`next_cursor`）id、狀態和日期——**絕不含個人資料**。使用 `updated_since` 只同步發生變化的內容。在不包含此功能的方案上，回應為 `403 plan_limit`。

## secret 輪換

在管理後臺中（“重新產生 secret”）：新的 secret 只顯示一次，**舊的 secret 繼續有效 24 小時**，因此您可以在不中斷服務的情況下更新系統。如果懷疑 secret 已外洩，請撤銷該用戶端：它的所有工作階段會立即結束。

## 安全

- secret 多次輸錯，用戶端會被鎖定 15 分鐘。
- 每個權杖都繫結在用戶端所屬的協會上：其他協會的會員卡只會顯示為“找不到”。
- 使用剛輪換的憑證收到 `401` 時，SDK 會用新權杖重試一次。
- 回應採用 `application/problem+json`（參見[權限、錯誤與限制](https://developers.vimine.network/zh-hant/guides/scopes-and-errors/)）。
