# Intégration et vérification des cartes

> Le formulaire d’adhésion sur votre site, le profil public de l’association et la vérification des cartes en ligne et hors ligne.

URL: https://developers.vimine.network/fr/guides/embed-and-verify/

## Le formulaire d’adhésion sur votre site

Chaque association qui a activé l’adhésion publique a une page `https://app.vimine.network/enroll/<slug>`
qui peut être placée dans une iframe sur le site de l’association :

```html
<iframe src="https://app.vimine.network/enroll/your-slug" style="width:100%;min-height:900px;border:0"></iframe>
```

L’association doit indiquer le domaine du site parmi ses **domaines d’intégration** dans ses réglages. La page de
connexion et de consentement de *Se connecter avec Vimine*, en revanche, **ne peut pas** être placée dans une iframe :
elle s’affiche toujours en pleine page sur vimine.network.

`?locale=en` ouvre le formulaire en anglais (l’élément `<vimine-enroll>` le transmet de lui-même, à partir de la
langue de la page).

### L’apparence du formulaire

Le formulaire peut prendre les couleurs de votre site. Une page dans une iframe ne voit pas le CSS de la page qui
l’accueille : le style voyage donc dans l’adresse.

| Paramètre | Valeurs | |
|---|---|---|
| `scheme` | `light`, `dark` | le thème de la page ; sans lui, celui de l’arrière-plan ou du visiteur |
| `accent` | une couleur hexadécimale (`1e3a5f`) | boutons, liens, champs actifs et nom de l’association |
| `background` | une couleur hexadécimale, ou `transparent` | le fond derrière les encadrés ; `transparent` laisse voir votre page |
| `font` | `inter`, `roboto`, `open-sans`, `montserrat`, `nunito`, `lora`, `merriweather`, `roboto-slab`, `system` | la police de toute la page, champs compris, parmi celles que Vimine sert lui-même (`system` : celle de l’appareil) |

```html
<iframe src="https://app.vimine.network/enroll/your-slug?scheme=light&accent=1e3a5f&background=transparent"
        style="width:100%;min-height:900px;border:0"></iframe>
```

Les autres valeurs sont ignorées. Avec `<vimine-enroll>`, il n’y a pas d’adresse à écrire : définissez
`--vimine-scheme`, `--vimine-accent`, `--vimine-background` et `--vimine-typeface` dans votre CSS
([éléments](https://developers.vimine.network/fr/sdk/elements/)). Avec `transparent`, choisissez le thème qui se lit sur votre page : les encadrés du
formulaire ont leur propre fond, le texte en dehors repose sur le vôtre.

### Une personne qui a déjà un compte Vimine

Une seule identité sert pour toutes les associations : une personne qui a déjà un compte n’a pas à remplir le
formulaire de nouveau. Ouverte directement, la page `…/enroll/<slug>` reconnaît la session et affiche **un seul
écran** — le type d’adhésion, le statuto et la notice d’information sur la vie privée de *cette* association, et
seulement ce que cette association demande en plus (l’adresse, si elle a déclaré la recueillir). Le statuto et la
notice sont acceptés à chaque fois : rien ne passe d’une association à une autre.

Dans une iframe, la session ne peut pas être lue : un navigateur tient une page encadrée à l’écart du même site ouvert
directement. Le formulaire encadré propose donc *Vous avez déjà un compte Vimine ? Continuer sur Vimine*, qui ouvre la
page dans un nouvel onglet. Si vous écrivez l’iframe à la main et utilisez `sandbox`, incluez `allow-popups` et
`allow-popups-to-escape-sandbox`, sinon cet onglet reste dans le bac à sable ; `<vimine-enroll>` le fait déjà.

## Le profil public

`GET /v1/aps/{slug}` renvoie le profil public de l’association — identité visuelle, types d’adhésion avec leurs
cotisations, statuto en vigueur — si vous voulez construire votre propre parcours :

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

const { aps, membership_types, statuto } = await publicApi().association('your-slug');
```

Aucun identifiant. Depuis une page où le membre a fait *Se connecter avec Vimine*, vous pouvez ensuite recueillir la
demande :

```js
const { aps, membership_types, statuto, privacy_notice } = await publicApi().association('your-slug');

await vimine.membership.request({
    membership_type,
    accept_statuto: { version: statuto.version },
    // la notice d’information de l’association, que votre page a affichée
    privacy_notice: privacy_notice && { version: privacy_notice.version, sha256: privacy_notice.sha256, locale: privacy_notice.locale },
    // l’adresse, si l’association en recueille une (aps.member_address: 'optional' | 'required')
    address: { line: 'Via Roma 1', postal_code: '20100', city: 'Milano', province: 'MI' }
});
```

Ce qu’une association demande à une personne qui adhère ne dépend pas de la porte par laquelle arrive la demande :

- **la notice d’information.** Tant que l’association en a une en vigueur que la personne n’a pas encore vue,
  `privacy_notice` est obligatoire : sans elle, `422 validation_failed` sur le champ `privacy_notice` ; avec une version
  qui n’est plus en vigueur, `422 privacy_notice_outdated`. `vimine.legal.acceptances()` dit ce qui reste à confirmer ;
  le texte est `GET /v1/aps/{slug}/privacy-notice`. La prise de connaissance est conservée comme une ligne de preuve
  liée à cette adhésion.
- **l’adresse.** `aps.member_address` dit si l’association la recueille : avec `required`, une demande sans `address`
  répond `422 validation_failed` sur le champ `address` ; avec `none`, elle est ignorée.
- **qui est la personne.** Quand le profil n’est pas encore identifié — ni code fiscal, ni date, lieu et pays de
  naissance — la demande répond `422 identification_required` : complétez le profil avec `vimine.me.update(...)` et
  répétez (voir [Membres sans code fiscal](https://developers.vimine.network/fr/guides/members-without-cf/#identification_required)).

:::caution[Modifié le 2 octobre 2026]
Jusqu’à la version `2026-09-27` de l’API, cette demande n’exigeait ni la notice ni l’adresse. Une intégration qui
l’appelle sans `privacy_notice` pour une association ayant une notice en vigueur reçoit désormais `422`.
:::

## Vérification des cartes

La carte numérique est un **jeton signé** (`vmt_…`) que le membre montre sous forme de code QR. Elle peut être vérifiée
de deux façons.

### En ligne

Sans identifiants, sans données personnelles dans la réponse :

```
GET https://api.vimine.network/v1/verify/vmt_…
→ { "status": "active", "valid_until": "2027-03-31T22:59:59.000Z" }
   ou
→ { "status": "inactive", "reason": "expired" }
   ou
→ { "status": "inactive", "reason": "not_yet_valid", "valid_from": "2026-09-19T19:00:00.000Z" }
```

`not_yet_valid` : la carte est authentique et l’adhésion est active, mais l’organisation a fixé un délai entre
l’admission et le moment où la carte peut être utilisée. `valid_from` dit quand elle commence à faire foi ; jusque-là,
à l’entrée, c’est une carte qui ne compte pas. Le même instant figure dans le jeton (`nbf`), si bien qu’un scanner hors
ligne arrive seul à la même réponse.

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

const { status } = await publicApi().verifyCard(tokenReadFromQr);
```

### Hors ligne

Le jeton est signé en **Ed25519** ; les clés publiques sont sur
`https://api.vimine.network/.well-known/vimine-card-keys.json` (aussi `GET /v1/card-keys`, ou
`publicApi().cardKeys()`). Un scanner vérifie **trois choses** avec la clé en cache : la signature, la fenêtre de
validité (`nbf`/`exp`, 90 jours au plus, puis le jeton change) et que l’état dans le payload est actif —
**`s === "active"`**. Puis il se resynchronise en ligne quand le réseau revient. Un `kid` inconnu
signifie que la clé a changé : rechargez les clés.

Le jeton change à chaque changement d’état, à chaque renouvellement et, de toute façon, tous les 90 jours : une vieille
capture d’écran n’ouvre rien pour toujours.

Une carte n’existe que pour une adhésion **active** : tant que la demande est en attente (admission ou cotisation), et
quand l’adhésion est suspendue ou révoquée, l’API n’émet aucun jeton — `card` vaut `null` dans
`GET /v1/me/membership`, et `GET /v1/me/membership/card`, l’image, l’e-mail et le lien Google Wallet répondent
`409 not_active`. Vérifier `s` reste obligatoire : cela rejette les jetons émis avant cette règle et tout jeton qui ne
déclare pas un état actif. Respectez aussi `nbf` : une organisation peut décider qu’une carte devient utilisable
quelques heures après l’admission, et c’est là que cet instant voyage.

:::caution
La vérification hors ligne dit que le jeton était **authentique et dans sa fenêtre** au moment de son émission, pas que
la carte est active en cet instant précis : une suspension survenue ensuite ne se voit qu’en ligne. Resynchronisez dès
que possible.
:::

## Code fiscal

Le code fiscal est **facultatif** : une personne qui n’en a pas adhère avec sa date, son lieu et son pays de naissance
(voir [Membres sans code fiscal](https://developers.vimine.network/fr/guides/members-without-cf/)). Quand il y en a un, `POST /v1/validate/fiscal-code`
(ou `publicApi().validateFiscalCode(...)`) vérifie la structure et le caractère de contrôle d’un codice fiscale italien
et, si vous passez aussi les données personnelles, leur cohérence avec le code — et dit ce que le code lui-même
contient :

```js
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' }
//   ou
// { valid: false, error: 'mismatch', mismatches: ['date'], hints: [], birth_date: '1980-01-01', birth_country: 'IT' }
```

`error` vaut `invalid` (structure ou caractère de contrôle), `mismatch` (un code bien formé qui est en désaccord avec
les données envoyées) ou `null` ; `hints` contient `names_swapped` quand le code correspond une fois le nom et le
prénom intervertis. Pour une personne née à l’étranger, envoyez `country` (ISO 3166-1 alpha-2) : son code ne contient
que l’État, et `place` — la ville — n’est pas comparé.
