Introduzione
Architettura e composizione del sistema

Il sistema di pagamento e fatturazione Agile è composto da quattro microservizi coordinati. Ogni componente può essere usato in modo indipendente o come parte del flusso end-to-end acquisto → fattura → SDI.

billing-ms

https://lg231.certisource.it/api/billing/

Abbonamenti, pacchetti onboarding, Stripe Checkout, compliance scheduler settimanale, FatturaPA automatica, impostazioni piattaforma.

payment-ms v2

https://[app].certisource.it/api/payment/

Multi-provider: Revolut Pay, Open Banking, SEPA Direct Debit, Stripe. Configurazione per-tenant con chiavi cifrate AES-256.

FatturaPA Service

https://certisource.it/fattura-service.php

Generazione XML FatturaPA 1.2.2 (TD01, TD04…), validazione schema. Auth: X-API-Key infragruppo.

SDI Service

https://certisource.it/sdi-service.php

Trasmissione fatture allo SDI via OpenAPI.it. Polling stato, ricevute AT/MC/NS. Idempotente su numero fattura.

Autenticazione
Header richiesti per ogni chiamata
Tutti gli endpoint (eccetto /health e /webhooks/) richiedono autenticazione dual-header: X-API-Key (tenant API key) + Authorization: Bearer {JWT}.
Request Headers
X-API-Key: lg231-demo-api-key-2026-secure
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
HeaderObbligatorioDescrizione
X-API-KeyREQUIREDAPI Key del tenant — identifica e autentica il tenant
AuthorizationREQUIREDJWT Bearer — contiene user_id, tenant_id, role
Content-Typerequired per POST/PUTDeve essere application/json
Stripe-Signaturesolo webhook StripeHeader HMAC inviato automaticamente da Stripe
X-Cron-Secretsolo cron endpointSecret condiviso per proteggere il cron job
Servizi & URL
Endpoint base e porte
ServizioURL Esterno (HTTPS)Porta Docker
billing-mshttps://lg231.certisource.it/api/billing/3058
payment-ms v2https://[app].certisource.it/api/payment/per app
FatturaPA Servicehttps://certisource.it/fattura-service.php
SDI Servicehttps://certisource.it/sdi-service.php
Health check — Tutti i servizi espongono GET /health senza autenticazione.
billing-ms: curl https://lg231.certisource.it/api/billing/health
Flusso End-to-End
Da acquisto pacchetto a fattura trasmessa allo SDI
1

Checkout Stripe

POST /billing/checkout/{n} — billing-ms crea una Stripe Checkout Session e restituisce checkout_url. Il frontend redirige il cliente.

2

Webhook Stripe

POST /billing/webhook — Stripe invia checkout.session.completed. billing-ms verifica la firma, marca il pacchetto paid, poi chiama automaticamente generateFattura().

3

Generazione FatturaPA

POST https://certisource.it/fattura-service.php?action=genera — billing-ms invia cedente + cessionario + righe. Il servizio ritorna XML FatturaPA 1.2.2 in base64 + hash_sha256.

4

Salvataggio XML

billing-ms salva fattura_xml, fattura_hash, fattura_numero e fattura_at nella tabella billing_packages.

5

Download XML (opzionale)

GET /billing/fattura/{packageId} — restituisce l'XML come file scaricabile con header Content-Disposition: attachment.

6

Trasmissione SDI (opzionale)

POST https://certisource.it/sdi-service.php?action=trasmetti — invia l'XML allo SDI via OpenAPI.it. Risponde con sdi_id per polling stato.


billing-ms — Endpoint
Base: https://lg231.certisource.it/api/billing
GET /billing/status JWT

Stato abbonamento del tenant corrente: subscription, pacchetti, scadenze.

cURL
curl https://lg231.certisource.it/api/billing/billing/status \
  -H "X-API-Key: {API_KEY}" \
  -H "Authorization: Bearer {JWT}"
Response 200
{
  "success": true,
  "data": {
    "subscription": {
      "id": 1, "status": "trial",
      "trial_ends_at": "2026-05-01T00:00:00Z",
      "packages_completed": 2, "packages_total": 8
    },
    "packages": [ /* array 8 pacchetti */ ],
    "days_remaining": 60
  }
}
POST /billing/checkout/{n} JWT

Crea una Stripe Checkout Session per il pacchetto n (1-8) — €150. Ritorna checkout_url a cui redirigere il browser.

cURL
curl https://lg231.certisource.it/api/billing/billing/checkout/1 \
  -H "X-API-Key: {API_KEY}" \
  -H "Authorization: Bearer {JWT}" \
  -H "Content-Type: application/json" \
  -X POST
Response 200
{
  "success": true,
  "data": { "checkout_url": "https://checkout.stripe.com/c/pay/cs_..." }
}
POST /billing/webhook Stripe-Signature

Webhook Stripe. Gestisce checkout.session.completed: marca pacchetto paid e genera FatturaPA automaticamente. Deve rispondere sempre HTTP 200.

L'URL del webhook da configurare nel dashboard Stripe è: https://lg231.certisource.it/api/billing/billing/webhook
GET /billing/fattura/{packageId} JWT

Scarica il file XML FatturaPA del pacchetto. Risponde con Content-Type: application/xml e header Content-Disposition: attachment.

cURL
curl -o fattura.xml \
  https://lg231.certisource.it/api/billing/billing/fattura/1 \
  -H "X-API-Key: {API_KEY}" \
  -H "Authorization: Bearer {JWT}"
GET PUT /billing/admin/platform-settings JWT admin

Legge o aggiorna le impostazioni della piattaforma: dati cedente FatturaPA (P.IVA, indirizzo, regime fiscale…) e IBAN incasso. I valori sono salvati nel DB con priorità su .env.

PUT body
{
  "cedente_piva":        "07776161213",
  "cedente_nome":        "Agile Technology SRL",
  "cedente_indirizzo":   "Via Roma 1",
  "cedente_cap":         "80100",
  "cedente_comune":      "Napoli",
  "cedente_provincia":   "NA",
  "cedente_iban":        "IT60X0542811101000000123456",
  "cedente_regime":      "RF01",
  "bonifico_iban":       "IT60X0542811101000000123456",
  "bonifico_bic":        "SELBIT2BXXX",
  "bonifico_beneficiario":"231 Agile"
}
GET Response
{
  "fattura_ready": true,
  "bonifico_ready": true,
  "fattura_api_key_set": true,
  "settings": { /* tutte le chiavi con label e source (db|env|empty) */ }
}
POST /billing/cron/check-compliance X-Cron-Secret

Verifica compliance settimanale: sospende tenant che hanno saltato una settimana, scade trial expirate. Da eseguire 1 volta/giorno via cron.

crontab
# /etc/cron.d/lg231-billing — ogni giorno alle 08:00
0 8 * * * root curl -s -X POST \
  http://127.0.0.1:3058/billing/cron/check-compliance \
  -H "X-Cron-Secret: {CRON_SECRET}" \
  >> /var/log/lg231-billing-cron.log 2>&1

payment-ms v2 — Endpoint
Multi-provider (Revolut · Stripe · SEPA DD) — configurazione per-tenant
GET /payment-methods JWT

Restituisce i metodi di pagamento disponibili per il provider configurato del tenant, con label, icone e disponibilità SEPA.

Response 200
{
  "provider": "revolut",
  "methods": [
    { "id": "revolut_pay", "label": "Revolut Pay", "recommended": true },
    { "id": "open_banking", "label": "Open Banking", "recommended": false },
    { "id": "sepa_debit", "label": "SEPA Direct Debit", "recommended": false }
  ]
}
POST /payments JWT

Crea un link di pagamento via Revolut Pay, Open Banking o carta. Ritorna payment_url a cui redirigere il cliente.

Request body
{
  "amount":      15000,      // centesimi (€150.00)
  "currency":    "EUR",
  "description": "Pacchetto Onboarding 231 #3",
  "reference":   "PKG-2026-003",
  "method":      "revolut_pay",  // revolut_pay | open_banking | card | sepa_debit
  "return_url":  "https://lg231.certisource.it/#billing"
}
POST /mandates JWT

Crea un mandato SEPA Direct Debit. Il cliente firma una volta e i futuri addebiti avvengono automaticamente. Ritorna mandate_url per la firma digitale.

Request body
{
  "customer_name":  "Mario Rossi",
  "customer_email": "mario@esempio.it",
  "iban":           "IT60X0542811101000000123456",
  "description":   "Abbonamento 231 Agile"
}
Response 201
{
  "mandate_id":  "mnd_abc123",
  "mandate_url": "https://revolut.com/mandate/sign/...",
  "status":      "pending_mandate_signature",
  "iban_masked": "IT60****3456"
}
POST /mandates/{mandateId}/charge JWT

Addebita il cliente via SEPA DD su un mandato attivo. Richiede status: active.

Request body
{
  "amount":      15000,
  "currency":    "EUR",
  "description": "Pacchetto Onboarding #4",
  "reference":   "PKG-2026-004"
}
PUT /config/provider JWT admin

Configura il provider di pagamento del tenant. Le API key vengono cifrate con AES-256-CBC prima di essere salvate nel DB.

Request body
{
  "provider":        "revolut",  // revolut | stripe
  "api_key":         "sk_live_...",
  "webhook_secret":  "whsec_..."
}

FatturaPA Service & SDI
CertiSource — auth: X-API-Key infragruppo
POST https://certisource.it/fattura-service.php?action=genera

Genera XML FatturaPA 1.2.2. Ritorna xml_base64 (file completo in base64) e hash_sha256.

Request body
{
  "cedente": {
    "piva":      "07776161213",
    "nome":      "231 Agile",
    "indirizzo": "Via Roma 1",
    "cap":       "80100",
    "comune":    "Napoli",
    "provincia": "NA",
    "regime":    "RF01"
  },
  "cessionario": {
    "nome":      "TechnoSteel S.r.l.",
    "piva":      "12345678901",
    "indirizzo": "Via Industria 42",
    "cap":       "20100",
    "comune":    "Milano",
    "provincia": "MI",
    "pec":       "fatture@technosteel.it"
  },
  "righe": [{
    "descrizione":      "231 Agile — Pacchetto Onboarding #1",
    "quantita":         1,
    "prezzo_unitario":  150.00,
    "aliquota_iva":     22
  }],
  "numero":   "FT-2026-0001-01",
  "tipo":     "TD01",
  "formato": "FPR12"
}
Response 200
{
  "xml_base64":  "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0i...",
  "hash_sha256": "a3f4b2c1d9e8f7a6b5c4d3e2f1a0b9c8..."
}
POST https://certisource.it/fattura-service.php?action=valida

Valida un XML FatturaPA (base64). Risponde con valid: true/false e lista errori.

Request body
{ "xml_base64": "PD94bWwg..." }
POST https://certisource.it/sdi-service.php?action=trasmetti

Trasmette fattura allo SDI via OpenAPI.it. Idempotente su fattura_numero: se già trasmessa restituisce il record esistente.

Request body
{
  "xml_base64":    "PD94bWwg...",
  "fattura_numero": "FT-2026-0001-01",
  "tenant_code":   "lg231"
}
Response 200
{
  "sdi_id":       "SDI-2026-001234",
  "stato":        "inviata",  // inviata | AT | MC | NS | errore
  "trasmessa_at": "2026-03-01T09:00:00Z"
}

Configurazione & Setup
Variabili d'ambiente billing-ms
.env billing-ms (produzione)
# Database billing
DB_HOST=lg231-mysql
DB_NAME=lg231_billing_db
DB_USER=lg231_user
DB_PASS=Lg231Agile2026!
AUTH_DB_NAME=lg231_auth_db

# JWT (allineato con tutti gli altri ms)
JWT_SECRET=0a76926c6fdfb49a60dc70de1b2f13cb...

# Stripe live keys
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_PACKAGE=price_...   # €150 one-time

# FatturaPA — CertiSource infragruppo
FATTURA_API_KEY=cs_fat_8bce32c3b95fa878...

# Dati cedente (fallback .env, meglio DB via admin UI)
FATTURA_CEDENTE_PIVA=07776161213
FATTURA_CEDENTE_NOME="231 Agile"
FATTURA_CEDENTE_REGIME=RF01

# Cron protection
CRON_SECRET=lg231-billing-cron-secret-2026

# URL redirect Stripe
SUCCESS_URL=https://lg231.certisource.it/#view-billing?payment=success
CANCEL_URL=https://lg231.certisource.it/#view-billing?payment=cancel
Dati cedente via UI admin — I dati FatturaPA del cedente possono essere inseriti dall'admin direttamente nell'interfaccia (view Billing → Impostazioni Piattaforma) e vengono salvati nel DB con priorità sul .env. Non è necessario modificare file di configurazione.
Setup Stripe — checklist una-tantum
1

Crea prodotto "Pacchetto Onboarding 231 Agile"

Dashboard Stripe → Products → Add product → One-time price €150.00 → copia price_xxx

2

Crea Webhook endpoint

Developers → Webhooks → https://lg231.certisource.it/api/billing/billing/webhook → events: checkout.session.completed

3

Aggiorna .env su Hetzner e rebuild

STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_PACKAGEdocker compose build billing-ms && docker compose up -d billing-ms

Codici di Errore
Formato risposta errore standard
Response errore
{
  "success": false,
  "error":   "Descrizione errore leggibile"
}
HTTPSignificatoCausa tipica
400Bad RequestBody mancante o campi obbligatori assenti
401UnauthorizedX-API-Key non valida o JWT scaduto/invalido
402Payment RequiredAbbonamento sospeso o scaduto (auth-ms)
403ForbiddenRuolo insufficiente (es: non admin)
404Not FoundRisorsa non trovata (es: fattura non ancora generata)
409ConflictMandato già esistente per il cliente
500Server ErrorErrore PHP, DB non raggiungibile, cURL timeout