Skip to main content

Subscription Model & License Management

Overview​

Visla GPS utilizza un modello di subscription basato su licenze per dispositivo. Ogni dispositivo attivo richiede una licenza valida. Il sistema gestisce automaticamente upgrade, downgrade e sospensione dispositivi per garantire che l'utente rimanga sempre nei limiti del proprio piano.

Concetti Chiave​

Licenze vs Dispositivi​

  • Licenza: Permesso di tracciare un dispositivo attivamente
  • Dispositivo Attivo: Dispositivo che invia posizioni e viene tracciato
  • Dispositivo Sospeso: Dispositivo associato all'account ma non tracciato (non consuma licenza)

Stati Dispositivo​

StatoDescrizioneConsuma LicenzaVisibile in App
AttivoTracciato normalmenteβœ… SΓ¬βœ… SΓ¬
SospesoAssociato ma in pausa❌ Noβœ… SΓ¬ (badge arancione)
RimossoDissociato dall'account❌ No❌ No

Scenari Utente (iOS)​

1. Utente Nuovo (0 Licenze, 0 Dispositivi)​

Situazione Iniziale:

  • Licenze: 0
  • Dispositivi: 0

Comportamento:

  • βœ… PuΓ² navigare l'app liberamente
  • βœ… Nessun modal bloccante
  • ❌ NON puΓ² aggiungere dispositivi

Flusso Aggiunta Dispositivo:

  1. Utente clicca "Aggiungi Dispositivo"
  2. Inserisce token Visla
  3. PRIMA del claim, app controlla licenze
  4. Alert: "Nessuna Licenza Attiva"
    • Messaggio: "Per aggiungere dispositivi devi prima acquistare almeno una licenza"
    • Bottoni: "Acquista Licenza" (blu) | "Annulla" (grigio)

Codice iOS:

// In AddDeviceView.claimDevice()
let status = try await ApiClient.shared.getLicenseStatus()

if status.allowed == 0 {
showNoLicenseModal = true
return
}

2. Utente con Licenze Sufficienti (2 Licenze, 1 Dispositivo)​

Situazione Iniziale:

  • Licenze: 2
  • Dispositivi Attivi: 1
  • Disponibili: 1

Comportamento:

  • βœ… PuΓ² aggiungere dispositivi normalmente
  • βœ… Nessun modal bloccante
  • βœ… PuΓ² aggiungere fino a 1 dispositivo aggiuntivo

Flusso Aggiunta Dispositivo:

  1. Utente clicca "Aggiungi Dispositivo"
  2. Inserisce token Visla
  3. Check licenze: 1 < 2 βœ… OK
  4. Device aggiunto con successo
  5. Nuovo stato: 2 licenze, 2 dispositivi attivi

3. Utente al Limite (2 Licenze, 2 Dispositivi, Prova ad Aggiungere 3Β°)​

Situazione Iniziale:

  • Licenze: 2
  • Dispositivi Attivi: 2
  • Disponibili: 0

Comportamento:

  • ❌ NON puΓ² aggiungere dispositivi
  • Alert preventivo prima del claim

Flusso Aggiunta Dispositivo:

  1. Utente clicca "Aggiungi Dispositivo"
  2. Inserisce token Visla
  3. PRIMA del claim, app controlla licenze
  4. Alert: "Limite Dispositivi Raggiunto"
    • Messaggio: "Hai 2 dispositivi attivi e 2 licenze. Per aggiungere altri dispositivi devi acquistare piΓΉ licenze o sospendere un dispositivo esistente"
    • Bottoni: "Acquista Licenze" (blu) | "Annulla" (grigio)

Codice iOS:

if status.active >= status.allowed {
showLimitReachedModal = true
return
}

4. Downgrade: PiΓΉ Dispositivi che Licenze (3 Dispositivi, 2 Licenze)​

Situazione Iniziale:

  • Licenze: 2 (dopo downgrade da 3)
  • Dispositivi Attivi: 3
  • Squilibrio: +1 dispositivo

Comportamento:

  • ⚠️ Modal bloccante all'apertura app
  • Utente DEVE risolvere lo squilibrio

Flusso:

  1. App si apre
  2. checkLicenseStatus() rileva: 3 > 2
  3. Modal: "Dispositivi Oltre il Limite"
    • Messaggio: "Hai 3 dispositivi attivi ma solo 2 licenze. Scegli cosa fare:"
    • Opzioni:
      • "Acquista Licenze" (blu) β†’ Vai a piano superiore
      • "Sospendi Dispositivi" (arancione) β†’ Apre DeviceSelectionModal

DeviceSelectionModal:

  • Mostra lista di tutti i dispositivi
  • Utente seleziona quali 2 mantenere attivi
  • Gli altri vengono sospesi automaticamente
  • Badge arancione "🟠 Sospeso" sui dispositivi sospesi

Codice iOS:

// In MainTabView.checkLicenseStatus()
if status.active > status.allowed {
showSubscriptionRequired = true
}

Backend:

# In devices/license_utils.py
def suspend_excess_devices(user_id: int, device_ids_to_keep: List[int]):
# Sospende tutti i device NON in device_ids_to_keep
# Imposta suspended=True in user_device_attributes

5. Upgrade: Da 2 a 5 Licenze​

Situazione Iniziale:

  • Licenze: 2
  • Dispositivi Attivi: 2
  • Dispositivi Sospesi: 1

Flusso:

  1. Utente acquista upgrade (2 β†’ 5 licenze)
  2. Backend aggiorna allowed_licenses via webhook
  3. Nuovo stato: 5 licenze, 2 attivi, 3 disponibili
  4. βœ… PuΓ² riattivare dispositivi sospesi
  5. βœ… PuΓ² aggiungere fino a 3 nuovi dispositivi

Riattivazione Dispositivo Sospeso:

  • Utente va in Device Settings di un dispositivo sospeso
  • Sezione "Gestione Licenza" mostra stato "🟠 Dispositivo Sospeso"
  • Bottone verde "Riattiva Dispositivo" (se licenze disponibili)
  • Conferma: "Il dispositivo verrΓ  riattivato e riprenderΓ  il tracciamento"
  • Backend imposta suspended=False
  • Badge cambia da "🟠 Sospeso" a "🟒 Online/πŸ”΄ Offline"

Sospensione Manuale:

  • Utente va in Device Settings di un dispositivo attivo
  • Sezione "Gestione Licenza" mostra stato "βœ… Dispositivo Attivo"
  • Bottone arancione "Sospendi Dispositivo"
  • Conferma: "Il dispositivo verrΓ  sospeso e non verrΓ  piΓΉ tracciato"
  • Backend imposta suspended=True
  • Libera una licenza per altri dispositivi

Restrizioni Provider​

Regola: Un Provider per Utente​

Importante: Un utente che inizia una subscription con un provider (Apple, Google, Stripe) DEVE continuare con quello stesso provider. Non Γ¨ possibile mescolare provider.

Esempi​

βœ… Scenario Valido​

  1. Utente si registra con Apple
  2. Acquista piano Basic (1 licenza) via Apple
  3. Fa upgrade a Pro (5 licenze) via Apple
  4. Fa downgrade a Basic via Apple
  5. Tutto tramite Apple βœ…

❌ Scenario NON Valido​

  1. Utente si registra con Apple
  2. Acquista piano Basic via Apple
  3. Prova a fare upgrade via Google Play
  4. Backend rifiuta: "Hai giΓ  una subscription attiva con Apple"

Cambio Provider​

Per cambiare provider, l'utente DEVE:

  1. Disdire completamente la subscription con il provider corrente
  2. Aspettare la scadenza naturale del periodo pagato
  3. Dopo la scadenza, puΓ² acquistare con un nuovo provider

Esempio:

1. Utente ha subscription Apple (scade 15 Gennaio)
2. Disdice da Apple il 5 Gennaio
3. Subscription rimane attiva fino al 15 Gennaio
4. Dal 16 Gennaio puΓ² acquistare con Google Play

Messaggi Backend​

Il backend Γ¨ configurato per gestire questi casi con messaggi chiari:

# In billing/routes/subscriptions.py
if existing_provider and existing_provider != new_provider:
raise ConflictError(
f"Hai giΓ  una subscription attiva con {existing_provider}. "
f"Per cambiare provider devi prima disdire la subscription corrente."
)

Logica Backend​

Endpoint Chiave​

GET /api/billing/license-status​

Restituisce lo stato corrente delle licenze:

{
"allowed": 5, // Licenze totali del piano
"active": 3, // Dispositivi attivi
"suspended": 1, // Dispositivi sospesi
"total": 4 // Totale dispositivi (attivi + sospesi)
}

POST /api/devices/{device_id}/suspend​

Sospende un dispositivo:

# Imposta suspended=True in user_device_attributes
# Il device rimane visibile ma non tracciato
# Non consuma licenza

POST /api/devices/{device_id}/reactivate​

Riattiva un dispositivo sospeso (se licenze disponibili):

if active_count >= allowed_licenses:
raise BadRequestError("Nessuna licenza disponibile")

# Imposta suspended=False

POST /api/devices/select-active​

Seleziona quali dispositivi mantenere attivi durante downgrade:

# Riceve: device_ids_to_keep
# Sospende tutti gli altri
# Usato da DeviceSelectionModal

Webhook Subscription​

Quando un utente cambia piano (upgrade/downgrade):

# In billing/webhooks.py
@router.post("/stripe/webhook")
async def stripe_webhook():
# 1. Riceve evento da Stripe/Apple/Google
# 2. Aggiorna allowed_licenses nel database
# 3. Se downgrade e active > allowed:
# - Invia notifica push all'utente
# - Utente deve aprire app e risolvere

Copertura Scenari​

βœ… Scenari Coperti​

ScenarioiOSAndroidWebBackend
Nuovo utente (0 lic, 0 dev)βœ…πŸ”„ TODOπŸ”„ TODOβœ…
Aggiunta device senza licenzeβœ…πŸ”„ TODOπŸ”„ TODOβœ…
Aggiunta device con licenzeβœ…βœ…βœ…βœ…
Aggiunta oltre limiteβœ…πŸ”„ TODOπŸ”„ TODOβœ…
Downgrade con squilibrioβœ…βœ…βœ…βœ…
Upgradeβœ…βœ…βœ…βœ…
Sospensione dispositivoβœ…βœ…βœ…βœ…
Riattivazione dispositivoβœ…βœ…βœ…βœ…
Cambio provider (bloccato)βœ…βœ…βœ…βœ…

πŸ”„ Prossimi Passi​

Android:

  • Implementare controllo licenze pre-claim in AddDeviceActivity
  • Aggiungere alert "No License" e "Limit Reached"

Web:

  • Implementare controllo licenze pre-claim in AddDeviceModal
  • Aggiungere modal differenziati per scenari

Diagramma Flusso Decisionale​


Note Implementative​

iOS​

File Chiave:

  • MainTabView.swift - Check licenze all'apertura app
  • AddDeviceView.swift - Validazione pre-claim
  • SubscriptionRequiredModal.swift - Modal squilibrio
  • DeviceSelectionModal.swift - Selezione dispositivi da mantenere

Logging:

Logger.shared.info("=== checkLicenseStatus START ===")
Logger.shared.info("License status", context: [
"allowed": status.allowed,
"active": status.active,
"suspended": status.suspended
])

Backend​

File Chiave:

  • billing/routes/subscriptions.py - Gestione subscription
  • devices/license_utils.py - Logica sospensione/riattivazione
  • devices/routes/devices.py - Endpoint suspend/reactivate
  • billing/webhooks.py - Webhook provider

Database:

-- user_device_attributes
ALTER TABLE user_device_attributes
ADD COLUMN suspended BOOLEAN DEFAULT FALSE;

Supporto​

Per domande o problemi relativi al modello di subscription:

  • Backend: Vedi billing/ e devices/license_utils.py
  • iOS: Vedi MainTabView.swift e AddDeviceView.swift
  • Android: Vedi SubscriptionRequiredDialog.kt e DeviceSelectionDialog.kt
  • Web: Vedi subscription-required-modal.tsx e device-selection-modal.tsx