跳转到内容

类型

查看 Markdown

这些类型由 @vimine/sdk(以及 /server)导出,与 OpenAPI 文档一一对应:字段名就是 JSON 中的字段名。完整的结构见 API 参考;这里列出主要的类型。

type Scope =
| 'profile:read' | 'profile:write'
| 'membership:read' | 'membership:request' | 'statuto:accept' | 'network:manage'
| 'memberships:read' | 'verify:read'
| 'identity:signup' | 'enroll:write' | 'checkins:write' | 'webhooks:manage'
| 'events:read' | 'registrations:read' | 'registrations:write';
type MembershipStatus = 'pending' | 'active' | 'suspended' | 'revoked' | 'rejected';
/** 会员卡此刻的效力。 */
type MembershipVerdict = 'valid' | 'expired' | 'not_yet_valid' | 'statuto_required' | 'inactive';
interface Me {
sub: string; // 在您的协会中的标识(vms_…)
email: string;
email_verified: boolean;
first_name: string | null;
last_name: string | null;
phone: string | null;
dob: string | null; // YYYY-MM-DD
gender: 'M' | 'F' | null; // 仅用于统计:不与税号比对
birthplace: string | null; // 市镇(在意大利出生)或城市,自由文本(在国外出生)
birth_country: string | null; // ISO 3166-1 alpha-2
codicefiscale: string | null; // 可选:一个人可以没有税号
codicefiscale_check: CodiceFiscaleCheck | null; // 当事人的身份识别方式;null = 尚未识别
locale: Locale | null; // 当事人选择的语言;null = 从未选择
updated_at: string | null;
}
/** Vimine 支持的一种语言。API 在接受语言的地方也接受带地区的标签(`en-GB` → `en`,`zh-TW` → `zh-Hant`),并将其归并。 */
type Locale = 'it' | 'en' | 'zh-Hans' | 'zh-Hant' | 'fr' | 'es' | 'de' | 'pt-BR';
interface LocaleParam { locale?: Locale | string } // 读取接口的 ?locale=——参见“语言”指南
/** 一个人如何被识别:通过税号,或者通过出生日期、出生地和出生国——两者不能都没有。 */
type CodiceFiscaleCheck = 'matched' | 'mismatch_confirmed' | 'absent';
interface MeUpdate { // PATCH /v1/me
phone?: string | null;
dob?: string | null;
gender?: 'M' | 'F' | null;
birthplace?: string | null;
birth_country?: string | null; // ISO 3166-1 alpha-2
codicefiscale?: string | null; // 可以之后再补充,或重新设为 null
codicefiscale_confirmed?: boolean; // 只写:当事人确认一个不一致的税号(在 `confirmable: true` 之后)
locale?: Locale | null; // null 清除所选语言;Vimine 不支持的语言返回 422 `unsupported`
}
interface Membership {
id: string;
status: MembershipStatus;
verdict: MembershipVerdict;
subdate: string | null;
issued_at: string | null;
expires_at: string | null;
membership_type: { uid: string; cod: string; name: string } | null;
statuto: {
current_version: number | null;
current_sha256: string | null;
accepted_version_id: string | null;
accepted_at: string | null;
pending: boolean; // 会员接受章程之后,又发布了更新的章程
overdue: boolean; // ……而且重新接受的期限已过
};
pending_reasons: Array<'approval' | 'payment'>; // 仍在等待的原因,按顺序:先入会审批,再缴纳会费
payment_open: boolean; // 现在可以缴纳会费吗?(获准入会后才开放缴费)
valid_from: string | null; // 会员卡何时可以使用(协会设定的间隔)
usable: boolean; // 会员卡此刻是否可在入场时使用
quota: { amount: number; currency: string; paid: boolean } | null;
payment_instructions: string | null;
expires_in_days: number | null;
renewal: { window_open: boolean; requested_at: string | null }; // 自助续期:到期前 60 天开放
network_opt_in: boolean;
card: { token: string; nbf: number; exp: number } | null; // 二维码的 vmt_… 令牌及其时间窗口(unix 秒)
}
interface Card { token: string; nbf: number; exp: number; status: MembershipStatus }
interface MembershipRequest { // POST /v1/me/membership
membership_type?: string; // uid;协会只提供一种类型时可省略
accept_statuto: { version: number; sha256?: string };
}
type MembershipRequestResult =
| { created: true; status: 'pending'; id: string }
| { already_requested: true; status: MembershipStatus };
interface MembershipEvents {
items: Array<{ from: MembershipStatus | null; to: MembershipStatus; reason: string | null; changed_at: string; channel: string }>;
next_cursor: string | null;
}
interface StatutoInfo {
uid: string;
version: number;
approvaldate: string | null;
sha256: string | null;
content: string | null;
document_url: string | null;
accepted: boolean;
accepted_at: string | null;
overdue: boolean;
}
interface Connection {
client_id: string;
name: string;
kind: 'public' | 'native' | 'confidential' | 'first_party';
scopes: Scope[];
granted_at: string;
}
interface Verification {
status: 'active' | 'inactive';
valid_until?: string;
valid_from?: string; // 当 reason 为 'not_yet_valid' 时:会员卡真实且有效,从此时起可以使用
reason?: 'expired' | 'invalid' | 'not_yet_valid';
}
type AdmissionModel = 'board' | 'provisional' | 'automatic'; // 协会如何接纳会员
interface AssociationPublicConfig {
aps: {
slug: string; name: string;
picture_url: string | null;
citta: string | null; provincia: string | null;
enroll_enabled: boolean;
accepting_requests: boolean; // 入会已开放,并且此刻还能再容纳一位会员
invitation_only: boolean; // 内测:只接纳受邀请的人(accepting_requests 为 false)
// 协会如何接纳会员——请事先告知申请人。board:由理事会决定,之后缴纳会费;
// provisional:立即临时入会,之后追认;automatic:条件一满足即生效。
// delay_hours:从获准入会到会员卡可以使用之间的小时数(0 = 立即)
admission: { model: AdmissionModel; delay_hours: number };
approval_required: boolean; // 已弃用:当 admission.model 为 'board' 时为 true——请读取 `admission`
payment_instructions: string | null; // 如何缴纳会费,在协会发布了说明时提供
member_address: MemberAddressPolicy; // 协会是否收集会员地址——对所有会员都收集,或对所有会员都不收集
// 公共利益活动(《第三部门法典》第 5 条):字母编号、表述、是否为主要活动
activity_sectors: Array<{ cod: string; description: string; prevalent: boolean }>;
// 根据官方名单已在 RUNTS 登记的信息,否则为 null
registry: {
section: 'APS' | 'ODV' | 'EF' | 'IS' | 'RA' | 'SOMS' | 'ALTRI' | null;
number: string | null; registered_on: string | null; checked_on: string | null;
} | null;
};
membership_types: Array<{
uid: string; cod: string; name: string; description: string | null;
quota_amount: number | null; currency: string;
expiry_model: 'solar_year' | 'rolling_12' | 'rolling_24' | 'prorated_solar' | 'custom';
network_eligible: boolean;
}>;
statuto: { uid: string; version: number; approvaldate: string | null; sha256: string | null; content: string | null; document_url: string | null } | null;
}
type MemberAddressPolicy = 'none' | 'optional' | 'required';
interface FiscalCodeCheck {
code: string; first_name?: string; last_name?: string; gender?: 'M' | 'F'; date?: string; place?: string;
country?: string; // 出生国:在国外出生的人,税号中只包含国家,`place` 不参与比对
}
type FiscalCodeMismatch = 'lastName' | 'firstName' | 'gender' | 'date' | 'place';
type FiscalCodeHint = 'names_swapped';
interface FiscalCodeResult {
valid: boolean;
error: 'invalid' | 'mismatch' | null; // 'invalid':结构或校验码有误;'mismatch':与所发送的信息不一致
mismatches: FiscalCodeMismatch[];
hints: FiscalCodeHint[];
birth_date: string | null; // 税号中包含的出生日期(YYYY-MM-DD)
birth_country: string | null; // 税号中包含的出生国(在意大利市镇出生则为 IT)
}
interface CardKeys { keys: Array<{ kty: string; crv: string; x: string; kid: string; use: string; alg: string }> }
interface MembershipStatusInfo { // GET /v1/memberships/{id}/status
id: string;
status: MembershipStatus;
verdict: MembershipVerdict;
valid_until: string | null;
membership_type: { cod: string } | null;
}
interface SignupInput { // POST /v1/signup
email: string;
first_name: string;
last_name: string;
phone?: string | null;
dob?: string | null; // YYYY-MM-DD
gender?: 'M' | 'F' | null;
birthplace?: string | null;
birth_country?: string | null; // ISO 3166-1 alpha-2——对于在国外出生的人:税号中只包含国家
codicefiscale?: string | null;
codicefiscale_confirmed?: boolean; // 当事人确认一个不一致的税号(422 `codicefiscale_mismatch` 且 `confirmable: true`)
accept_terms?: LegalRef; // 在您的网站上接受的账户使用条款——可选,现行版本
acknowledge_privacy?: LegalRef; // 在您的网站上阅读的平台隐私声明——可选,规则相同
}
interface EnrollInput extends SignupInput { // POST /v1/enroll
codicefiscale?: string | null; // 可选:税号,或 dob + birthplace + birth_country,两者不能都没有
address?: MemberAddress; // 户籍地或住所地,在 `aps.member_address` 不为 `none` 时提供
membership_type?: string; // 只有一种类型时可省略
accept_statuto: { version: number; sha256?: string };
privacy_notice?: { version: number; sha256?: string }; // 协会发布了隐私声明时必填
}
interface MemberAddress {
kind?: 'residence' | 'domicile'; // 默认 'residence'
line: string; // 街道和门牌号
postal_code?: string | null;
city: string;
province?: string | null; // 仅限意大利地址(两个字母)
country?: string; // ISO 3166-1 alpha-2,默认 'IT'
}
interface EnrollIdentification { // 收到 409 identification_required 之后调用 POST /v1/enroll/confirm
dob?: string; birthplace?: string; birth_country?: string; codicefiscale?: string; codicefiscale_confirmed?: boolean;
}
interface CardCheckinResult { // 会员卡的 POST /v1/checkins
kind: 'card';
verdict: MembershipVerdict;
status: MembershipStatus;
membership_id: string;
valid_until: string | null;
valid_from: string | null;
identity_check: IdentityCheck | null; // 给入口值守人员的提示:不属于 `verdict`,不限制任何操作
checked_in_at: string;
}
/** 'pending':尚需查看证件;'verified':已查看;'flagged':已查看,但与信息不符。 */
type IdentityCheck = 'pending' | 'verified' | 'flagged';
interface FieldError { // `VimineError.errors` 中的一条记录
field?: string; code?: string; message?: string;
mismatches?: FiscalCodeMismatch[]; hints?: FiscalCodeHint[]; confirmable?: boolean; // 仅限 `codicefiscale_mismatch`
}
interface LegalRef { version: string; sha256?: string } // accept_terms / acknowledge_privacy(可选,现行版本)
interface MyAcceptances { // GET /v1/me/acceptances (legal:read)
items: AcceptanceRecord[]; // at, action, channel, kind, title, version, sha256, rendered_sha256, locale, association
pending: { platform: PendingDocument[]; privacy_notice: PendingDocument | null };
}
interface AcceptanceInput { kind: string; version: string | number; sha256?: string } // POST /v1/me/acceptances (legal:accept)
interface Accepted { status: 'accepted' } // 响应,始终相同
interface EnrollConfirmResult { // POST /v1/enroll/confirm
status: MembershipStatus;
already: boolean; // 已经有会员资格或申请
pending_reasons: Array<'approval' | 'payment'>; // 按顺序:先入会审批,再缴纳会费
payment_open: boolean;
valid_from: string | null;
association: { name: string; slug: string };
event?: EnrolledPlace | null; // 仅当链接来自从活动打开的入会页面时提供:名额的结果
}
interface EnrolledPlace { // 随入会申请一起申请的名额(“活动与报名”指南 › 尚未入会的人)
slug: string; title: string; starts_at: string; timezone: string; place: string | null;
registration_type: { uid: string; name: string; amount: number; currency: string };
state: 'confirmed' | 'reserved' | 'held' | 'unavailable'; // held:申请本身在等待邮箱地址确认
waiting: Array<'membership' | 'contribution'>; // `reserved` 名额在等待什么
reasons: string[]; // 为什么是 `unavailable`(报名资格的原因)
reserved_until: string | null;
payment_instructions: string | null;
already: boolean; // 名额此前已被保留
}
interface SandboxMembers { slug: string; members: Array<{ email: string; name: string; status: MembershipStatus; expires_at: string | null; statuto_accepted: boolean }> }
interface SandboxReset { password: string; members: Array<{ email: string; name: string; status: MembershipStatus }> }
interface TokenResponse {
access_token: string;
token_type: 'Bearer';
expires_in: number;
refresh_token?: string;
scope: string;
sub?: string; // 客户端令牌中没有此字段
}

Spec 由 openapi/v1.json 生成(npm run spec:types,在 CI 中检查):文档中的每个 schema 都对应一个类型,另有 Spec.operations(id → 方法和路径)。上面的类型仍是文档化的公开接口;Spec 供直接对接 API 的开发者使用。

import type { Spec } from '@vimine/sdk';
let p: Spec.Problem; const [m, path] = Spec.operations.listMemberships;