# Paiement, support et comptabilité

Les secrets ne sont jamais placés dans une offre de facturation ni renvoyés au navigateur. Un administrateur de plateforme les saisit dans `https://console.xaere.io/#platform-settings`; Xaere Core les chiffre avant stockage et la console n'affiche ensuite que leur état configuré/non configuré. La clé maître `XAERE_PLATFORM_SECRETS_KEY_B64` reste uniquement dans `/srv/xaere/secrets/runtime.env` (root, `0600`). Les variables historiques du fichier d'environnement restent des valeurs de repli pendant la transition.

Les valeurs non secrètes Proxi/Dolibarr (URL, boîte, entité, TVA et activation
du worker de brouillons) se règlent aussi dans la page administrateur. Core les
valide et les applique dynamiquement; le fichier root reste seulement le repli.

## Support Proxi

La console crée des tickets par `POST /v1/support/tickets` avec une session OIDC et un `request_id` unique (`supreq_…`). Xaere appelle alors Proxi côté serveur. La clé Proxi doit être limitée à la mailbox concernée et aux scopes `conversations:read` et `conversations:write`. L’historique `GET /v1/support/tickets` est paginé par curseur opaque, 50 tickets par défaut et 100 au maximum ; la console ne charge les pages anciennes que sur action explicite.

La lecture d'un ticket s'effectue par
`GET /v1/support/tickets/{ticket_id}`. Core vérifie d'abord l'organisation et
la correspondance immuable avec la conversation Proxi, puis ne renvoie que les
messages visibles au client. Les notes internes et les URL de pièces jointes
Proxi restent côté support.

Configurer :

```dotenv
PROXI_SUPPORT_BASE_URL=https://support.proxi.systems/api/proxi
PROXI_SUPPORT_API_KEY=proxi_...
PROXI_SUPPORT_MAILBOX_ID=5
PROXI_SUPPORT_WEBHOOK_SECRET=<secret-partage>
```

La mailbox de production Xaere est `Xaere Support / support@xaere.io`, ID `5`.
La clé dédiée doit rester limitée à cette mailbox, aux scopes
`conversations:read` et `conversations:write`, et à l'IP du backend Xaere quand
elle est fixe. Elle ne doit pas recevoir `webhooks:manage`, `customers:*` ou
`mailboxes:read` pour le flux applicatif normal.

Le callback Proxi est `https://api.xaere.io/v1/support/webhooks/proxi`. Il vérifie `X-Proxi-Signature` (HMAC SHA-256 du corps brut), déduplique `X-Proxi-Delivery` et synchronise le statut (`active`, `pending` ou `closed`) du ticket correspondant par son ID Proxi ou son identifiant externe Xaere. Les charges webhook restent internes et ne sont jamais exposées à la console.

Valider séparément le canal sortant et le callback entrant :

```bash
/srv/xaere/app/deploy/e2e/verify-proxi-readiness.sh
/srv/xaere/app/deploy/e2e/verify-provider-webhook-drill.sh proxi
```

Le premier appel effectue uniquement un `GET` borné à une conversation et ne
crée aucun ticket. Le second utilise un reçu synthétique signé, vérifie son
rejeu idempotent puis supprime et contrôle les lignes temporaires. Aucun secret
n'est affiché par ces commandes.

## Offres et paiements

Les offres sont créées côté administration, jamais depuis le navigateur. Stripe utilise une référence de prix `price_…`; Flouci utilise un montant entier en millimes TND et une durée `entitlement_months` comprise entre 1 et 36. Une session interne `bco_…` lie l’offre, l’organisation et le fournisseur. Seul un webhook vérifié peut activer l’abonnement.

Attribuer le rôle de realm Keycloak `xaere_platform_admin` uniquement aux
opérateurs de la plateforme. Il permet de créer et désactiver des plans,
offres et fournisseurs via les routes `/v1/billing/admin/*`. Il affiche aussi
l'écran Settings, qui permet la rotation des identifiants mais ne peut jamais
relire les secrets. Une offre
Flouci est obligatoirement `TN` / `TND`, avec un montant en millimes et une
durée de droit explicite ; une
offre Stripe référence un prix `price_…` créé dans Stripe.

```dotenv
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
FLOUCI_PUBLIC_KEY=<public-token>
FLOUCI_PRIVATE_KEY=<private-token>
XAERE_CORE_PUBLIC_URL=https://api.xaere.io
```

Le registre interne des webhooks conserve uniquement une preuve minimale :
fournisseur, identifiant et type d'événement, empreinte SHA-256 du corps,
référence de checkout Xaere et statut/montant nécessaires. Les objets Stripe
complets et les détails Flouci (`name`, `phone_number`, `email`) sont traités
en mémoire mais ne sont pas persistés dans ce registre. Cette minimisation ne
change ni la vérification cryptographique Stripe ni la relecture serveur
Flouci.

Lors de la génération Flouci, Core vérifie aussi que le
`developer_tracking_id` retourné correspond exactement au checkout `bco_…`
créé par Xaere. Une réponse fournisseur non JSON, un identifiant non conforme,
une URL non HTTPS ou une référence différente échoue fermée et n'active aucun
droit.

Références fournisseur vérifiées le 15 août 2026 :
[Stripe Checkout](https://docs.stripe.com/api/checkout/sessions/create) et
[Flouci Generate Payment](https://docs.flouci.com/api-reference/generate-transaction) /
[Verify Payment](https://docs.flouci.com/api-reference/verify-transaction).

Configurer Stripe pour `https://api.xaere.io/v1/billing/webhooks/stripe`. Configurer Flouci avec le callback `https://api.xaere.io/v1/billing/webhooks/flouci`; chaque nouvelle notification, ainsi que toute réception encore en attente après un échec, est vérifiée auprès de Flouci avant activation. Une réception déjà traitée est acquittée idempotemment sans répéter l'appel fournisseur. Les nouvelles vérifications sont limitées par Redis (`XAERE_FLOUCI_WEBHOOK_RATE_LIMIT_PER_MINUTE`, 30 par minute par défaut).

Après avoir enregistré les secrets Stripe et Proxi, lancer en root :

```bash
/srv/xaere/app/deploy/e2e/verify-provider-webhook-drill.sh stripe,proxi
```

Ce contrôle non financier envoie d'abord une signature invalide, puis un
événement synthétique valide et son rejeu. Il vérifie le routage HTTPS public,
la signature, le ledger terminal et l'idempotence, puis supprime ses reçus dans
un bloc `finally`. Il ne crée aucun checkout, abonnement, ticket client ni
droit SaaS, et n'affiche jamais les secrets. Flouci ne peut pas être simulé de
cette manière : sa notification doit toujours être confirmée par l'API Flouci.
Effectuer donc séparément un vrai paiement sandbox Flouci borné et contrôler
ensuite l'abonnement obtenu avant de supprimer le tenant de test.

Après un checkout Stripe vérifié, Xaere conserve uniquement la référence client
`cus_…` côté Core. Un utilisateur ayant le rôle organisationnel de facturation
peut demander `POST /v1/billing/portal?organization_id=org_…` : Core crée une
URL temporaire Stripe Customer Portal et ne renvoie ni secret ni référence
client au navigateur.

Ajouter aussi les événements Stripe `checkout.session.completed`,
`checkout.session.async_payment_succeeded`, `customer.subscription.created`,
`customer.subscription.updated`, `customer.subscription.deleted`,
`invoice.paid` et `invoice.payment_failed` au même endpoint webhook. Xaere
vérifie chaque signature avant de mettre à jour le statut serveur ; les
événements inconnus ou plus anciens ne peuvent pas réactiver un accès.

Core fixe ses appels REST Stripe à `2025-03-31.basil` et accepte aussi les
anciens objets webhook : la période est lue au niveau des éléments
d'abonnement Basil, avec compatibilité pour l'ancien champ au niveau de
l'abonnement. Un checkout `unpaid` n'accorde aucun droit. Avant l'ouverture
réelle, exécuter `deploy/e2e/verify-billing-reconciliation-drill.sh`, puis les
transactions contrôlées Stripe test et Flouci test.

## Dolibarr

Dolibarr 18.0.8 est une projection comptable asynchrone : il reçoit une tâche `dolibarr.invoice.create` seulement après l’activation vérifiée, mais ne décide jamais de l’accès Xaere. Le module **API REST** et le module de factures doivent être actifs. L’URL reste configurable, avec le défaut demandé :

```dotenv
DOLIBARR_BASE_URL=https://er.proxi.systems
DOLIBARR_API_KEY=<cle-utilisateur-dediee>
DOLIBARR_ENTITY_ID=
```

Xaere appelle ensuite `https://er.proxi.systems/api/index.php/...` avec `DOLAPIKEY`. Donner à cette clé uniquement les permissions nécessaires pour les tiers/factures et valider les champs exacts dans l’API Explorer de cette instance avant de lancer le worker comptable.

Le worker est volontairement désactivé par défaut. Pour chaque organisation à
facturer, un administrateur plateforme associe d’abord son identifiant de tiers
Dolibarr via la section d’administration de facturation de
`https://console.xaere.io/` ou via
`PUT /v1/billing/admin/dolibarr/customers/{organization_id}`. La liste affichée
ne contient que l’organisation, l’identifiant numérique du tiers et la date de
mise à jour ; aucune clé Dolibarr n’est renvoyée au navigateur.
Avant toute activation, vérifier sans mutation l'URL REST, la clé, l'entité et
le droit de lecture des factures :

```bash
/srv/xaere/app/deploy/e2e/verify-dolibarr-readiness.sh
```

Avant d'activer le worker, utiliser un tiers Dolibarr exclusivement réservé aux
tests. Cette première commande reste en lecture seule et retourne un jeton lié
au tiers, au montant borné et au taux TVA actuellement configuré :

```sh
/srv/xaere/app/deploy/e2e/verify-dolibarr-controlled-draft.sh \
  --prepare --thirdparty-id ID_TIERS_TEST --amount 1.000
```

Après vérification manuelle, répéter avec `--execute` et le jeton exact indiqué
par `--prepare`. Le drill crée seulement un brouillon marqué
`xaere:controlled-draft:…`, vérifie son tiers, sa référence et son statut `0`,
puis le supprime par l'API Dolibarr 18.0.8 et confirme sa disparition. Il refuse
de fonctionner si la synchronisation est déjà active, si le montant dépasse
10 unités ou si le jeton ne correspond plus au taux TVA. Une preuve JSON
minimale, sans charge de facture, est conservée en mode `0600` dans le répertoire
de release ; un échec de nettoyage est signalé explicitement et n'active rien.

Ce contrôle exécute uniquement `GET /api/index.php/invoices?limit=1`, ne
retourne aucun contenu comptable et ne crée aucune ligne. Tester ensuite un
brouillon borné avec le tiers Xaere dédié, contrôler la ligne et la TVA dans
Dolibarr, puis supprimer ce brouillon avant d'activer le worker.
Ensuite seulement, activer :

```dotenv
DOLIBARR_TVA_RATE=<taux-valide-pour-votre-entite>
DOLIBARR_ENABLE_INVOICE_SYNC=true
```

Le worker crée un brouillon, ajoute exactement une ligne, et emploie la
référence externe `xaere:{outbox_id}` pour reprendre de manière idempotente
après un incident. Il ne valide pas la facture et n’enregistre pas de paiement
dans Dolibarr : cette étape reste contrôlée par la comptabilité.
