# Dokicasa SDK — Istruzioni di integrazione per Claude Code

Questo file e' progettato per essere consegnato a **Claude Code** (o ad altri
assistenti AI) come prompt di integrazione. Contiene tutto cio' che serve
all'assistente per integrare il Web Component `<dokicasa-form>` nel sito
del partner senza dover consultare la documentazione interattiva.

> **Per Claude/assistente AI:** Leggi tutto questo file prima di scrivere
> codice. Le sezioni "Vincoli", "Sicurezza" e "Common pitfalls" contengono
> regole non negoziabili. Se manca un parametro (slug del contratto,
> API key, secret), CHIEDI all'utente prima di inventarlo.

---

## TL;DR

L'SDK Dokicasa e' un **Web Component** distribuito come singolo file JS
(~50 KB gzip). Si integra in qualsiasi stack (React, Vue, Svelte, Astro,
WordPress, Laravel, HTML statico) con due righe:

```html
<script src="https://sdk.dokicasa.it/v1.js"></script>

<dokicasa-form
    slug="locazione-ad-uso-abitativo-4-4"
    api-key="pk_live_xxxxxxxx"
    user-token="eyJhbGc..."></dokicasa-form>
```

Lo `user-token` e' un **JWT short-lived** che il backend del partner DEVE
generare server-side (mai client-side) firmandolo con la `sk_live_...`.

---

## 1. Contesto

- **Cosa offre l'SDK:** rendering completo del form di creazione contratto
  immobiliare di Dokicasa (compilazione guidata multi-step, validazione,
  salvataggio bozze, firma elettronica, generazione PDF, invio).
- **Cosa NON deve fare il partner:** riprodurre i campi del form, gestire
  la validazione, salvare i dati, generare PDF, gestire la firma. Tutto
  questo e' opaco e gestito dall'SDK.
- **Cosa deve fare il partner:**
  1. Caricare lo script nel `<head>` o appena prima di `</body>`.
  2. Renderizzare `<dokicasa-form>` con i 3 attributi richiesti.
  3. Generare un JWT lato server per ogni sessione utente.
  4. (Opzionale ma consigliato) ascoltare gli eventi `load`, `saved`,
     `error` per analytics e UX feedback.
  5. (Opzionale) ricevere il webhook `contract.completed` sul proprio
     endpoint per aggiornare il proprio sistema.

---

## 2. Vincoli (non negoziabili)

- **MAI** mettere la `sk_live_...` nel codice frontend. Esporrebbe la
  possibilita' di firmare contratti a nome del partner.
- **MAI** generare il JWT in browser. Sempre server-side.
- L'`api-key` (`pk_live_...`) puo' stare nel frontend MA solo se il
  dominio del partner e' nella whitelist `allowed_origins` configurata
  nel pannello Dokicasa.
- Il JWT scade in **15 minuti**. Per sessioni lunghe genera un endpoint
  che lo refresha (vedi sezione "Refresh JWT").
- Lo script SDK NON e' un'NPM package. Niente `npm install`, niente
  `import`. E' un `<script>` browser-side che registra il custom element
  e finisce.
- Il file `v1.js` va caricato da `https://sdk.dokicasa.it/v1.js` in
  produzione. L'URL `https://dokicasa.loc/contract-form-v2-sdk/v1.js`
  e' SOLO sviluppo Dokicasa interno e NON va distribuito ai partner.

---

## 3. Sicurezza

Lo schema di auth a due fattori e' modellato su Stripe:

| Chiave | Visibilita' | Dove vive | Cosa puo' fare |
|---|---|---|---|
| `pk_live_*` | pubblica | frontend (HTML/JS browser) | identificare il partner; valida solo se l'`Origin` e' in whitelist |
| `sk_live_*` | **SEGRETA** | solo backend partner, mai loggata, mai committata | firmare JWT user-token, generare webhook secret |
| `user-token` JWT | semi-pubblica (15 min validi) | inietttato nel HTML server-side renderizzato | identificare l'utente finale per quella sessione |
| `webhook_secret` | **SEGRETA** | solo backend partner | verificare HMAC dei webhook entranti |

Se l'API key viene rubata: il danno e' limitato dalla whitelist origini.
Se la `sk_live` viene rubata: emergenza, regenera subito dal pannello.

---

## 4. Quick start completo (PHP + Laravel)

### a) Composer

```bash
composer require firebase/php-jwt
```

### b) `.env`

```
DOKICASA_PK_LIVE=pk_live_xxxxxxxxxxxxxxxx
DOKICASA_SK_LIVE=sk_live_xxxxxxxxxxxxxxxx
```

### c) Controller

```php
namespace App\Http\Controllers;

use Firebase\JWT\JWT;
use Illuminate\Support\Facades\Auth;

class ContrattoController extends Controller
{
    public function form(string $slug)
    {
        $user = Auth::user();
        abort_unless($user, 401);

        $token = JWT::encode([
            'external_id' => (string) $user->id,
            'email'       => $user->email,
            'name'        => $user->full_name,
            'iss'         => 'partner_abc',       // = il tuo partner_slug Dokicasa
            'aud'         => 'dokicasa',
            'exp'         => time() + 900,        // 15 minuti
            'iat'         => time(),
        ], env('DOKICASA_SK_LIVE'), 'HS256');

        return view('contratto.form', [
            'slug'      => $slug,
            'apiKey'    => env('DOKICASA_PK_LIVE'),
            'userToken' => $token,
        ]);
    }
}
```

### d) Route

```php
Route::middleware('auth')->get('/contratto/{slug}', [ContrattoController::class, 'form']);
```

### e) Blade view (`resources/views/contratto/form.blade.php`)

```html
<!doctype html>
<html lang="it">
<head>
    <meta charset="utf-8">
    <title>Crea contratto</title>
    <script src="https://sdk.dokicasa.it/v1.js"></script>
</head>
<body>
    <dokicasa-form
        slug="{{ $slug }}"
        api-key="{{ $apiKey }}"
        user-token="{{ $userToken }}"></dokicasa-form>

    <script>
    document.querySelector('dokicasa-form')
        .addEventListener('saved', e => {
            console.log('Bozza salvata #' + e.detail.userMakesContractId);
        });
    </script>
</body>
</html>
```

---

## 5. Quick start (Node.js / Express)

```js
import express from 'express';
import jwt from 'jsonwebtoken';

const app = express();

app.get('/contratto/:slug', requireAuth, (req, res) => {
    const token = jwt.sign({
        external_id: String(req.user.id),
        email:       req.user.email,
        name:        req.user.fullName,
    }, process.env.DOKICASA_SK_LIVE, {
        issuer:    'partner_abc',
        audience:  'dokicasa',
        expiresIn: '15m',
    });

    res.send(`
        <!doctype html><html><head>
            <script src="https://sdk.dokicasa.it/v1.js"></script>
        </head><body>
            <dokicasa-form
                slug="${req.params.slug}"
                api-key="${process.env.DOKICASA_PK_LIVE}"
                user-token="${token}"></dokicasa-form>
        </body></html>
    `);
});
```

---

## 6. Quick start (React + Next.js)

### a) Endpoint API (`pages/api/dokicasa-token.ts`)

```ts
import jwt from 'jsonwebtoken';
import type { NextApiRequest, NextApiResponse } from 'next';
import { getServerSession } from 'next-auth';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    const session = await getServerSession(req, res, authOptions);
    if (!session) return res.status(401).end();

    const token = jwt.sign({
        external_id: session.user.id,
        email:       session.user.email,
    }, process.env.DOKICASA_SK_LIVE!, {
        issuer:    'partner_abc',
        audience:  'dokicasa',
        expiresIn: '15m',
    });

    res.json({ token, apiKey: process.env.NEXT_PUBLIC_DOKICASA_PK_LIVE });
}
```

### b) Componente React

```tsx
'use client';
import { useEffect, useRef, useState } from 'react';
import Script from 'next/script';

export default function ContractForm({ slug }: { slug: string }) {
    const ref = useRef<HTMLElement>(null);
    const [auth, setAuth] = useState<{ token: string; apiKey: string } | null>(null);

    useEffect(() => {
        fetch('/api/dokicasa-token').then(r => r.json()).then(setAuth);
    }, []);

    useEffect(() => {
        if (!ref.current) return;
        const handler = (e: any) => console.log('saved', e.detail);
        ref.current.addEventListener('saved', handler);
        return () => ref.current?.removeEventListener('saved', handler);
    }, [auth]);

    if (!auth) return <div>Caricamento...</div>;

    return (
        <>
            <Script src="https://sdk.dokicasa.it/v1.js" strategy="afterInteractive" />
            {/* @ts-expect-error custom element */}
            <dokicasa-form
                ref={ref}
                slug={slug}
                api-key={auth.apiKey}
                user-token={auth.token}
            />
        </>
    );
}
```

### c) Tipi TypeScript (`types/dokicasa.d.ts`)

```ts
declare namespace JSX {
    interface IntrinsicElements {
        'dokicasa-form': React.DetailedHTMLProps<
            React.HTMLAttributes<HTMLElement> & {
                slug: string;
                'api-key': string;
                'user-token': string;
                'contract-type'?: string;
                'api-base'?: string;
            },
            HTMLElement
        >;
    }
}
```

---

## 7. WordPress (no build)

Aggiungi nel `functions.php` del tuo tema:

```php
add_action('wp_enqueue_scripts', function () {
    wp_enqueue_script('dokicasa-sdk', 'https://sdk.dokicasa.it/v1.js', [], null, true);
});

add_shortcode('dokicasa_form', function ($atts) {
    $atts = shortcode_atts(['slug' => 'locazione-ad-uso-abitativo-4-4'], $atts);
    if (!is_user_logged_in()) return '<p>Accedi per compilare il contratto.</p>';

    $user  = wp_get_current_user();
    $token = dokicasa_generate_jwt($user);

    return sprintf(
        '<dokicasa-form slug="%s" api-key="%s" user-token="%s"></dokicasa-form>',
        esc_attr($atts['slug']),
        esc_attr(get_option('dokicasa_pk_live')),
        esc_attr($token)
    );
});

function dokicasa_generate_jwt(WP_User $user): string {
    // Richiede `composer require firebase/php-jwt` nel plugin.
    return \Firebase\JWT\JWT::encode([
        'external_id' => (string) $user->ID,
        'email'       => $user->user_email,
        'iss'         => 'partner_abc',
        'aud'         => 'dokicasa',
        'exp'         => time() + 900,
    ], get_option('dokicasa_sk_live'), 'HS256');
}
```

Poi nella pagina/post: `[dokicasa_form slug="locazione-ad-uso-abitativo-4-4"]`.

---

## 8. Attributi del custom element

| Attributo | Obbligatorio | Tipo | Default |
|---|---|---|---|
| `slug` | si | string | — |
| `api-key` | si | string `pk_live_*` o `pk_test_*` | — |
| `user-token` | si | string JWT | — |
| `contract-type` | no | string | `"default"` |
| `api-base` | no | URL string | `"https://api.dokicasa.it/api"` |

Lo `slug` deve essere uno di quelli abilitati per il partner (whitelist
nel pannello Dokicasa). Per la lista completa: pannello partner o
`GET https://api.dokicasa.it/partner-api/contracts` con `X-Api-Key`.

---

## 9. Eventi DOM

Ascoltali col solito `addEventListener` sul nodo `<dokicasa-form>`:

```js
const form = document.querySelector('dokicasa-form');

form.addEventListener('load', e => {
    // Schema caricato e form renderizzato.
    console.log('steps:', e.detail.steps);
});

form.addEventListener('saved', e => {
    // L'utente ha cliccato "Salva e continua dopo": bozza creata o aggiornata.
    console.log('bozza:', e.detail.userMakesContractId);
});

form.addEventListener('error', e => {
    // Errore di rete, schema, validazione, auth.
    console.error(e.detail.message, e.detail.status);
});
```

Tutti gli eventi sono `CustomEvent` standard: `bubbles: true`,
`composed: true` (escono dallo shadow), `cancelable: false`.

---

## 10. Webhook entranti (`contract.completed`)

Quando l'utente firma il contratto, Dokicasa fa un POST HTTPS al
`webhook_url` configurato nel pannello partner:

```http
POST https://partner.it/dokicasa/webhook HTTP/1.1
Content-Type: application/json
X-Dokicasa-Event: contract.completed
X-Dokicasa-Signature: t=1716393200,v1=5257a869e7e...
X-Dokicasa-Delivery: 01H8N3K9...

{
  "event": "contract.completed",
  "timestamp": "2026-05-22T14:32:00Z",
  "data": {
    "user_makes_contract_id": 12345,
    "contract_slug":          "locazione-ad-uso-abitativo-4-4",
    "external_id":            "user-12345",
    "pdf_url":                "https://api.dokicasa.it/contracts/12345/pdf"
  }
}
```

### Verifica della firma (PHP)

```php
function dokicasa_verify_signature(string $payload, string $header, string $secret): bool {
    if (!preg_match('/^t=(\d+),v1=([a-f0-9]+)$/', $header, $m)) return false;
    [$_, $timestamp, $signature] = $m;
    if (abs(time() - (int) $timestamp) > 300) return false; // anti replay 5 min
    $expected = hash_hmac('sha256', $timestamp . '.' . $payload, $secret);
    return hash_equals($expected, $signature);
}
```

### Idempotenza

Usa `X-Dokicasa-Delivery` come chiave di deduplica: salvalo in DB,
rifiuta i duplicati con `200 OK` (non `409`, sennò Dokicasa rietnta).

---

## 11. Refresh JWT (sessioni lunghe)

Il JWT scade in 15 minuti. Se l'utente sta compilando da piu' di quel
tempo, il prossimo "Salva" fallira'. Per evitarlo, espone un endpoint
che genera un nuovo JWT e aggiorna l'attributo:

```js
// Polling lato client ogni 10 minuti.
setInterval(async () => {
    const r = await fetch('/api/dokicasa-token');
    const { token } = await r.json();
    document.querySelector('dokicasa-form').setAttribute('user-token', token);
}, 10 * 60 * 1000);
```

In alternativa, ascolta l'evento `error` con `status === 401` e ricarica
il token on-demand.

---

## 12. Styling / brand override

Lo Shadow DOM blocca il CSS del sito host, ma espone variabili CSS
sull'host che il partner puo' sovrascrivere:

```css
dokicasa-form {
    --primary:      #1e293b;   /* navy del partner */
    --secondary:    #f97316;   /* arancio CTA del partner */
    --secondary-50: #fff7ed;   /* sfondo sezioni selezionate */
}
```

Variabili disponibili: `--primary`, `--primary-600`, `--primary-50`,
`--secondary`, `--secondary-600`, `--secondary-50`, `--ink-700`,
`--ink-500`, `--red`, `--green`, `--border`, `--bg`.

---

## 13. Common pitfalls (per evitare bug noti)

- **CORS:** se vedi `Access-Control-Allow-Origin` errors, il dominio non
  e' nella whitelist Dokicasa. Aggiungilo dal pannello (es. https://www.tuosito.it
  e https://staging.tuosito.it sono due entry separate).
- **JWT in URL:** mai mettere il JWT in query string. Sempre come
  attributo `user-token` o cookie HttpOnly.
- **CSP (Content-Security-Policy):** se il tuo sito ha CSP rigorosa,
  aggiungi `https://sdk.dokicasa.it` a `script-src` e
  `https://api.dokicasa.it` a `connect-src`.
- **iframe annidato:** lo SDK NON funziona dentro un `<iframe sandbox>`
  senza `allow-scripts allow-same-origin`.
- **Service Worker aggressivo:** se ti fa cache `v1.js`, rischi
  versioni stale dopo gli aggiornamenti. Usa SemVer pinned
  (`v1.2.3.js`) in produzione se la cosa ti preoccupa.
- **HTTPS obbligatorio:** in dev locale puoi usare `http://localhost`,
  ma da qualsiasi altro dominio serve HTTPS (i Custom Elements moderni
  non funzionano senza secure context per certe API).

---

## 14. Test locale prima di andare in prod

1. Usa la chiave `pk_test_...` (sandbox: nessuna fattura emessa,
   nessun contratto inviato all'Agenzia delle Entrate)
2. Aggiungi `http://localhost:3000` (o quello che usi) alla whitelist
3. Apri la pagina, completa un form, verifica:
   - `load` event esce (vedi console)
   - `saved` event esce dopo "Salva e continua dopo"
   - Il webhook arriva al tuo endpoint locale (usa `ngrok` o
     `cloudflared` per esporre la porta in HTTPS)

---

## 15. Risorse

- **Documentazione interattiva:** <https://sdk.dokicasa.it/>
- **Demo live:** <https://sdk.dokicasa.it/demo.html>
- **Pannello partner:** <https://dokicasa.it/partner>
- **Email integration team:** <integrations@dokicasa.it>
- **Status page:** <https://status.dokicasa.it/>

---

## 16. Checklist finale per Claude

Quando hai finito di integrare, verifica che:

- [ ] Lo script `https://sdk.dokicasa.it/v1.js` e' caricato una sola volta
- [ ] La `sk_live_*` non compare in NESSUN file del client/frontend
- [ ] L'endpoint che genera il JWT richiede autenticazione utente
- [ ] Il JWT contiene almeno `external_id`, `iss`, `aud`, `exp`
- [ ] Gli eventi `saved` e `error` sono ascoltati per analytics
- [ ] L'endpoint webhook valida `X-Dokicasa-Signature`
- [ ] La whitelist `allowed_origins` include il dominio di produzione
- [ ] CSP del sito include `sdk.dokicasa.it` (script-src) e
      `api.dokicasa.it` (connect-src)

Se manca anche uno solo di questi, **non considerare l'integrazione completa**.
