Patterns
Reusable buyers registry
Reference a buyer by `buyer_id` without breaking fiscal immutability
The `buyers` registry stores a buyer (SIRET, VAT, billing and shipping addresses) and reuses it via `buyer_id` on each invoice. Scope is strict per `(tenant_id, sub_tenant_id)` pair: a tenant never sees another's buyers (anti-IDOR via 404). Key compliance point: the `invoices.buyer_id` link is a soft UUID, WITHOUT cascade FK. The denormalised `buyer_*` columns snapshotted on the invoice remain the immutable source of truth — editing a buyer later NEVER affects already-emitted invoices (ISCA). On invoice creation, supply either `buyer_id` (the API re-snapshots the current state) or flat `buyer_*` fields (the API upserts a buyer by SIRET then email). The shipping address (BG-13) is emitted in Factur-X only when it differs from the billing address.
Key facts
- Strict scope (tenant_id, sub_tenant_id) — anti-IDOR 404
- `invoices.buyer_id` soft UUID, no cascade FK
- Denormalised `buyer_*` columns = immutable ISCA truth
- Deterministic upsert by SIRET (B2B) then email (B2C)
- BG-13 shipping address omitted when identical to billing
- Endpoints: `GET/POST/PATCH/DELETE /api/v1/buyers`
Code example
import { ScellApiClient } from '@scell/sdk'; // v3.5.0
const client = new ScellApiClient({ apiKey: process.env.SCELL_API_KEY! });
// 1. Enregistrer un acheteur réutilisable
const { data: buyer } = await client.buyers.create({
name: 'Acme SARL',
country: 'FR',
siret: '98765432109876',
vat_number: 'FR98765432109',
email: 'compta@acme.fr',
billing_address: { line1: '2 av. Demo', postal_code: '69001', city: 'Lyon', country: 'FR' },
// Adresse de livraison distincte → BG-13 émise dans le Factur-X
shipping_address: { name: 'Entrepôt Lyon', line1: '10 rue du Stock', postal_code: '69007', city: 'Lyon', country: 'FR' },
});
// 2. Facturer en référençant l'acheteur (snapshot immutable côté facture)
const invoice = await client.invoices.create({
format: 'factur-x',
profile: 'EN16931',
currency: 'EUR',
buyer_id: buyer.id,
lines: [{ description: 'Licence annuelle', quantity: 1, unit_price: 1200, vat_rate: 20 }],
});
console.log('Facture :', invoice.id, '→ buyer figé sur la facture');
// 3. Modifier le buyer plus tard n'altère PAS les factures déjà émises (ISCA)
await client.buyers.update(buyer.id, { email: 'finance@acme.fr' });