# 活動與報名

> 在您的網站上發布協會的活動，讓會員報名（公開活動也允許嘉賓報名），並用核驗會員卡的同一個掃描器辦理入場登記。

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

協會在管理後臺中發布的活動，以與會員卡相同的原則透過 API 提供：**headless 文案**（標題、摘要、markdown 描述、圖片、地點、日期、連結）以及每種報名類型的**即時餘量**——但絕不包括*誰*報了名。用語沿用義大利第三部門的習慣：稱為**報名**（iscrizione），有金額時稱為**參與費**（contributo）。

:::note[開始之前]
您的用戶端需要 `events:read`、`registrations:read` 和 `registrations:write` 權限（參見[權限、錯誤與限制](https://developers.vimine.network/zh-hant/guides/scopes-and-errors/)）。如需在不觸及真實資料的情況下試用，請使用[沙箱](https://developers.vimine.network/zh-hant/guides/sandbox/)：孿生協會可以像真實協會一樣發布活動。
:::

## 兩種可見性

| `visibility` | 誰能看到 | 誰能報名 |
|---|---|---|
| `members_only`（預設） | 只有持會員權杖或協會自己用戶端的權杖時可見 | 持有效會員卡的會員 |
| `public` | 任何人，無需登入：`GET /v1/aps/{slug}/events` | 會員，以及——當報名類型不要求會員卡時——**嘉賓** |

公開活動會向您網站的訪客展示活動文案和各報名類型的剩餘名額；僅限會員的活動只在登入後出現。

## 顯示活動

```js title="公開頁面，無需登入"
import { publicApi } from '@vimine/sdk';
const api = publicApi();
const { items } = await api.events.list('your-slug', { filter: 'upcoming' });
const event = await api.events.get('your-slug', 'jazz-night');
```

```js title="使用 Vimine 登入之後（包括僅限會員的活動）"
const { items } = await vimine.events.list();           // events:read
const event = await vimine.events.get('jazz-night');
```

每個活動都帶有 `registration_types[]`：`amount`（0 = 免費）、`currency`、`remaining`（剩餘名額，`null` = 不限）、`open`，以及在未開放時的 `reason`（`sold_out`、`registration_not_open`、`registration_closed`）。`status` 為 `published`、`completed`（日期已過）或 `cancelled`。

無需編寫程式碼：[`<vimine-events>`](https://developers.vimine.network/zh-hant/sdk/elements/) 元素會呈現活動清單和報名按鈕。

## 尚未入會的人

公開活動也能觸及非會員：這正是一個人決定入會的時刻。協會在 Vimine 上的入會頁面會**帶著該活動**開啟，當事人一步即可入會並預約名額：

```
https://app.vimine.network/enroll/your-slug?event=jazz-night
```

```js title="同一網址，在您的程式碼中產生"
import { eventEnrollUrl } from '@vimine/sdk/elements';
const url = eventEnrollUrl('https://app.vimine.network', 'your-slug', 'jazz-night', 'en');
```

名額的結果取決於會員資格所處的階段，頁面會告訴當事人：

| 會員資格 | 名額 |
|---|---|
| 立即生效（免費會員卡，無需審核） | 若活動報名免費，則**已確認** |
| 等待審核或會費 | 對於僅限會員的報名：名額**保留**至會員卡生效，屆時若報名免費則自動確認。對於向所有人開放的報名：立即確認，與非會員相同 |
| 活動報名需要支付參與費 | 名額**保留**至協會設定的期限：協會記錄參與費後即確認 |
| 電子郵件地址尚未確認 | 不保留任何名額：當事人透過郵件中的連結確認地址時才申請名額，並會收到一封郵件告知結果 |

已經是會員的人也使用同一個頁面：登入後點一個按鈕即可預約，無需填寫任何內容（`&signin=1` 會立即開啟登入）。這只適用於已發布且尚未舉行的 `public` 活動：如果是其他任何 slug，該頁面就是普通的入會頁面。

Vimine 上的活動公開頁面（`/agenda/your-slug/jazz-night`）和 `<vimine-events>` 已經向訪客提供了這一入口：“加入協會並預約”。

## 為會員報名

在顯示按鈕之前，先詢問 API 該會員能否報名——回應會說明不能報名的原因：

```js
const { can_register, types } = await vimine.events.eligibility('jazz-night');
// types[i].reasons: membership_required, membership_not_active, membership_not_yet_valid, sold_out, max_per_user, registration_closed, …
```

然後為其報名（`registrations:write`）：

```js
const reg = await vimine.registrations.create('jazz-night', { registration_type: types[0].uid, note: '我和姐姐一起來' });
if (reg.status === 'confirmed') drawQr(reg.token);           // 免費類型：已確認，QR Code已就緒
if (reg.status === 'reserved') show(reg.payment_instructions); // 需付參與費：名額保留至付款為止
```

- **免費**類型會立即確認並傳回 `token`：即會員在入場時出示的 QR Code 內容（請您自行繪製，使用您喜歡的函式庫）。
- **需付參與費**的類型會保留名額（`reserved_until`），並傳回協會的付款說明；秘書處記錄參與費後予以確認——會員會收到一封電子郵件，此後 `vimine.registrations.get(id)` 就會帶有 `token`。刷卡付款將透過 Stripe 提供。
- 如果會員的會員卡仍在等待生效，報名會保持 `reserved`，並在會員卡生效時自動確認。
- `422 not_eligible` 會在 `errors[]` 中帶有與報名資格查詢相同的原因。

會員的報名：`vimine.registrations.list({ filter: 'upcoming' })`；在活動的取消期限內（開始前 `registration.cancellation_hours` 小時）取消：`vimine.registrations.cancel(id)`——超過期限則傳回 `409 window_closed`。

## 嘉賓（公開活動）

沒有 Vimine 帳戶的訪客可以從您的伺服器（機密用戶端，`registrations:write`）報名參加**公開**活動，前提是報名類型不要求會員卡：

```js title="server"
await srv.registrations.guest('jazz-night', { email, first_name, last_name, registration_type });
// → 始終為 { status: 'accepted' }
```

不會建立任何帳戶。嘉賓會收到一封帶確認連結（有效期 24 小時）的電子郵件，連結指向 Vimine 上的活動頁面：免費類型會在那裡確認名額並立即顯示 QR Code；需付參與費的類型則保留名額，並顯示付款說明。如果您希望自己託管確認頁面，請用 `api.confirmRegistration(token)` 使用連結中的權杖。

名額一經確認——免費類型立即確認，其他情況在記錄參與費後確認——嘉賓會收到第二封電子郵件，內含他專屬的連結：它會開啟顯示入場 QR Code 的活動頁面，在活動結束之前一直有效（活動改期後仍然有效）。嘉賓沒有個人專區：連結就是他的名額，而不是 QR Code 本身（轉發的郵件並不等於名額）。如果您自己託管該頁面，請用 `api.viewRegistration(token)` 讀取名額（SDK 自 0.3.2 起）。

回應始終是 `202 { status: 'accepted' }`：API 從不透露某個地址是否已經報名。報名資格問題（`sold_out`，或僅限會員的類型傳回 `membership_required`）傳回 `422`。

:::caution[稅務]
根據義大利《第三部門法典》，**非會員**繳納的參與費屬於經營性收入：當活動被設為公開時，管理後臺會提醒這一點；每筆參與費都會根據付款時該報名所關聯的會員資格自動歸類。
:::

## 入場時

報名 QR Code 和會員卡一樣，是一個簽名權杖：同一個掃描器、同一個呼叫都能接受它。

```js title="server（閘門、掃描器、服務檯）"
const r = await srv.checkins.create(scannedToken);
if (r.kind === 'registration') {
    // r.verdict: valid | already_checked_in | not_confirmed | cancelled | event_cancelled | membership_invalid
    // r.first：首次通行時為 true；類型要求會員卡時帶有 r.membership_verdict
}
```

公開核驗介面也能回答報名權杖：`GET /v1/verify/{token}` → `{ status, kind: 'registration', event: { slug, starts_at } }`，不含個人資料。

## 從您的管理系統

某個活動的報名——id、狀態、類型、會員資格、入場登記，**不含個人資料**——透過 `srv.registrations.listForEvent(slug, { status, limit, cursor })` 取得（`registrations:read`，適用於支援 API 清單的方案）。姓名只保留在協會的管理後臺中。

## 參考

- [瀏覽器 SDK](https://developers.vimine.network/zh-hant/sdk/browser/#events) · [伺服器 SDK](https://developers.vimine.network/zh-hant/sdk/server/#events) · [`<vimine-events>`](https://developers.vimine.network/zh-hant/sdk/elements/) · [型別](https://developers.vimine.network/zh-hant/sdk/types/)
- HTTP API：[參考文件](https://developers.vimine.network/zh-hant/api/console/)中標籤為 `events` 和 `registrations` 的操作。
