嵌入與會員卡核驗
您網站上的入會表單
章節「您網站上的入會表單」每個開放了公開入會的協會都有一個頁面 https://app.vimine.network/enroll/<slug>,可以放進協會網站的 iframe 中:
<iframe src="https://app.vimine.network/enroll/your-slug" style="width:100%;min-height:900px;border:0"></iframe>協會必須在自己的設定中,把該網站的網域列入允許嵌入(iframe)的網域。而“使用 Vimine 登入”的登入和授權同意頁面則不能放進 iframe:它始終是 vimine.network 上的一個完整頁面。
?locale=en 會以英文開啟表單(<vimine-enroll> 元素會根據頁面的語言自動傳入該參數)。
表單的外觀
章節「表單的外觀」表單可以採用您網站的配色。iframe 中的頁面看不到外層頁面的 CSS,因此樣式要透過網址傳遞:
| 參數 | 取值 | |
|---|---|---|
scheme |
light、dark |
頁面的明暗模式;不指定時,跟隨背景或訪客的設定 |
accent |
十六進位顏色(1e3a5f) |
按鈕、連結、獲得焦點的欄位以及協會名稱 |
background |
十六進位顏色,或 transparent |
卡片後面的底色;transparent 會透出您的頁面 |
font |
inter、roboto、open-sans、montserrat、nunito、lora、merriweather、roboto-slab、system |
整個頁面(包括欄位)的字型,從 Vimine 自行託管的字型中選擇(system:裝置內建的字型) |
<iframe src="https://app.vimine.network/enroll/your-slug?scheme=light&accent=1e3a5f&background=transparent" style="width:100%;min-height:900px;border:0"></iframe>其他取值會被忽略。使用 <vimine-enroll> 時無需編寫網址:在您的 CSS 中設定 --vimine-scheme、--vimine-accent、--vimine-background 和 --vimine-typeface 即可(元素)。使用 transparent 時,請選擇在您的頁面上清晰可讀的明暗模式:表單的卡片有自己的底色,卡片之外的文字則直接落在您的頁面上。
已有 Vimine 帳戶的人
章節「已有 Vimine 帳戶的人」一個身份適用於所有協會:已有帳戶的人無需再次填寫表單。直接開啟時,頁面 …/enroll/<slug> 會識別工作階段,並只顯示一個頁面——本協會的會員卡類型、章程和隱私權聲明,以及本協會額外要求的內容(地址,如果協會聲明了要收集地址)。章程和隱私權聲明每次都需要接受:任何內容都不會從一個協會帶到另一個協會。
在 iframe 中無法讀取工作階段:瀏覽器會把嵌入框架中的頁面與直接開啟的同一網站隔離開來。因此,嵌入的表單會提供“已有 Vimine 帳戶?前往 Vimine 繼續”,在新分頁中開啟該頁面。如果您手寫 iframe 並使用了 sandbox,請加上 allow-popups 和 allow-popups-to-escape-sandbox,否則那個分頁仍會被困在沙箱中;<vimine-enroll> 已經這樣做了。
公開資料
章節「公開資料」如果您想建構自己的體驗,GET /v1/aps/{slug} 會傳回協會的公開資料——品牌形象、會員卡類型及其會費、現行章程:
import { publicApi } from '@vimine/sdk';
const { aps, membership_types, statuto } = await publicApi().association('your-slug');無需任何憑證。之後,在會員已透過“使用 Vimine 登入”登入的頁面上,您就可以收集入會申請:
const { aps, membership_types, statuto, privacy_notice } = await publicApi().association('your-slug');
await vimine.membership.request({ membership_type, accept_statuto: { version: statuto.version }, // 協會的隱私權聲明,即您的頁面所展示的那一份 privacy_notice: privacy_notice && { version: privacy_notice.version, sha256: privacy_notice.sha256, locale: privacy_notice.locale }, // 地址,在協會收集地址時傳入(aps.member_address: 'optional' | 'required') address: { line: 'Via Roma 1', postal_code: '20100', city: 'Milano', province: 'MI' }});協會對申請人的要求,與申請從哪個入口提交無關:
- 隱私權聲明:只要協會有一份當事人尚未閱讀的現行隱私權聲明,
privacy_notice就是必填的:缺少時,privacy_notice欄位上傳回422 validation_failed;版本已不再現行時,傳回422 privacy_notice_outdated。vimine.legal.acceptances()會告訴您還有哪些需要確認;文字在GET /v1/aps/{slug}/privacy-notice。閱讀確認會作為一條與該會員資格關聯的證明記錄儲存。 - 地址:
aps.member_address說明協會是否收集地址:為required時,不帶address的申請會在address欄位上傳回422 validation_failed;為none時,地址會被忽略。 - 當事人的身份:當個人資料尚未識別身份時——既沒有稅號,也沒有出生日期、出生地和出生國——申請傳回
422 identification_required:請用vimine.me.update(...)完善個人資料後重試(參見沒有義大利稅號的會員)。
會員卡核驗
章節「會員卡核驗」數位會員卡是一個簽名權杖(vmt_…),會員以 QR Code 的形式出示。有兩種核驗方式。
線上
章節「線上」無需憑證,回應中不含任何個人資料:
GET https://api.vimine.network/v1/verify/vmt_…→ { "status": "active", "valid_until": "2027-03-31T22:59:59.000Z" } 或→ { "status": "inactive", "reason": "expired" } 或→ { "status": "inactive", "reason": "not_yet_valid", "valid_from": "2026-09-19T19:00:00.000Z" }not_yet_valid:會員卡是真實的,會員資格也已生效,但機構設定了從獲准入會到會員卡可以使用之間的間隔。valid_from 說明它從何時開始算數;在此之前,在入場時它是一張不算數的會員卡。權杖中(nbf)也帶有同一時刻,因此離線掃描器自己就能得出相同的結果。
import { publicApi } from '@vimine/sdk';
const { status } = await publicApi().verifyCard(tokenReadFromQr);離線
章節「離線」權杖使用 Ed25519 簽名;公開金鑰位於 https://api.vimine.network/.well-known/vimine-card-keys.json(也可透過 GET /v1/card-keys 或 publicApi().cardKeys() 取得)。掃描器使用快取的公開金鑰核驗三件事:簽名、有效時段(nbf/exp,最長 90 天,之後權杖會輪換),以及酬載中的狀態為有效——s === "active"。網路恢復後再線上重新同步。遇到未知的 kid,說明金鑰已輪換:請重新載入公開金鑰。
權杖在每次狀態變化、每次續期時都會輪換,而且無論如何每 90 天輪換一次:一張舊截圖不可能永遠有效。
只有有效的會員資格才有會員卡:申請仍在等待(入會審核或會費)期間,以及會員資格被暫停或撤銷時,API 不簽發任何權杖——GET /v1/me/membership 中的 card 為 null,而 GET /v1/me/membership/card、會員卡圖片、電子郵件和 Google Wallet 連結都傳回 409 not_active。檢查 s 仍是必須的:它會拒絕在此規則之前簽發的權杖,以及任何未宣告有效狀態的權杖。也請遵守 nbf:機構可以決定會員卡在獲准入會幾小時後才可使用,這一時刻就記錄在那裡。
稅號
章節「稅號」稅號是可選的:沒有稅號的人可以憑出生日期、出生地和出生國入會(參見沒有義大利稅號的會員)。有稅號時,POST /v1/validate/fiscal-code(或 publicApi().validateFiscalCode(...))會檢查義大利稅號(codice fiscale)的結構和檢查碼;如果您同時傳入個人資訊,還會檢查它們與稅號是否一致——並說明稅號本身包含什麼:
const r = await publicApi().validateFiscalCode({ code: 'RSSMRA80A01H501U', first_name: 'Mario', last_name: 'Rossi', date: '1980-01-01', place: 'Roma', country: 'IT' });// { valid: true, error: null, mismatches: [], hints: [], birth_date: '1980-01-01', birth_country: 'IT' }// 或// { valid: false, error: 'mismatch', mismatches: ['date'], hints: [], birth_date: '1980-01-01', birth_country: 'IT' }error 為 invalid(結構或檢查碼有誤)、mismatch(格式正確但與所傳送資訊不一致的稅號)或 null;當姓和名互換後稅號能對上時,hints 中包含 names_swapped。對於在國外出生的人,請傳送 country(ISO 3166-1 alpha-2):他們的稅號中只包含國家,place(城市)不參與比對。