Blog
Feature
10 febbraio 20268 min

Abbonamenti RevenueCat: 16 casi limite e notti insonni

Punti chiave: Implementare abbonamenti in-app con RevenueCat sembra semplice: tre livelli, un SDK. In realtà, si tratta di 16 casi limite di modifiche al piano, obblighi legali francesi sulla visualizzazione del prezzo IVA inclusa e un singleton PurchaseService che deve gestire il ripristino degli acquisti in tempo reale. Ecco tutto ciò che la documentazione non dice.

Quando ho deciso di monetizzare TAMSIV, la scelta di RevenueCat è stata ovvia. Gestiscono la complessità degli store per te — le ricevute di Google Play, la convalida del server, il tracciamento degli abbonamenti. Beh, in teoria.

In pratica, ho passato tre settimane sull'implementazione. Non a causa di RevenueCat stesso (l'SDK è ben fatto), ma a causa di tutti i casi limite che nessuno menziona nei tutorial. Ecco l'esperienza completa.

Terminale di pagamento con carta con indicatore LED verde di conferma, illuminazione blu ambientale
Il pagamento sembra semplice dal lato utente. Dal lato sviluppatore, è un'altra storia.

Come strutturare i livelli di abbonamento di un'app AI?

TAMSIV offre tre piani:

  • Free — Accesso alle funzioni di base con limiti giornalieri. Abbastanza per scoprire l'app, non abbastanza per un uso intensivo.
  • Pro — Tutto sbloccato: generazione di immagini AI, STT cloud Deepgram (migliore del nativo in ambienti rumorosi), memo illimitati, tutte le voci TTS.
  • Team — Il livello collaborativo completo: gruppi gerarchici su 6 livelli, assegnazioni, checklist di gruppo, permessi avanzati.

La scelta di tre livelli non è arbitraria. Gli studi dimostrano che tre opzioni massimizzano la conversione — l'effetto di ancoraggio spinge gli utenti verso il piano intermedio. Il Free attira, il Pro converte, il Team monetizza i team.

Il file planLimits.ts

Dal lato codice, un file config/planLimits.ts centralizza tutte le feature gates. Ogni funzionalità verifica il piano attivo prima di essere eseguita:

// Esempio semplificato
const PLAN_LIMITS = {
  free:  { dailyVoiceTasks: 5,  aiImages: 0,  cloudSTT: false },
  pro:   { dailyVoiceTasks: -1, aiImages: 20, cloudSTT: true  },
  team:  { dailyVoiceTasks: -1, aiImages: 50, cloudSTT: true  },
};

Questo file unico è la fonte di verità. Nessuna condizione if (plan === 'pro') dispersa nel codice — tutto passa attraverso i limiti configurati. Quando modifico un piano, tocco un solo file.

Quali sono i 16 casi limite delle modifiche al piano?

È qui che diventa un incubo. Un utente può:

  • Aggiornare: Free → Pro, Free → Team, Pro → Team
  • Declassare: Team → Pro, Team → Free, Pro → Free
  • Cambiare periodo: Mensile → Annuale, Annuale → Mensile
  • Combinare i due: Aggiornamento + cambio di periodo
  • Annullare: Con accesso fino alla fine del periodo
  • Riattivare: Prima o dopo la scadenza
  • Ripristinare: Su un nuovo telefono

Ho contato 16 casi distinti. Per ognuno, bisogna gestire:

  1. Il momento dell'attivazione: Un upgrade ha effetto immediatamente. Un downgrade è differito alla fine del periodo corrente.
  2. Il prorata: Google Play calcola automaticamente il prorata per gli upgrade. Ma tu devi visualizzarlo correttamente nell'UI.
  3. L'aggiornamento delle feature gates in tempo reale: Quando un utente effettua un upgrade, le nuove funzionalità devono sbloccarsi istantaneamente, senza riavviare l'app.
  4. La visualizzazione corretta: Il piano giusto, la data di scadenza giusta, il prezzo giusto, lo stato giusto.

Tre giorni di test per coprire tutto. È noioso, metodico e assolutamente indispensabile. Un solo caso mancante, e ricevi un'email da un utente furioso che ha pagato per un Pro ed è ancora in Free.

Mani che tengono uno smartphone che mostra una schermata di prezzi con diversi livelli di abbonamento
La schermata dei prezzi: semplice per l'utente, complessa da implementare.

Come gestire gli obblighi legali francesi per gli abbonamenti?

Se vendi in Francia, la DGCCRF impone regole severe:

  • Prezzo IVA inclusa obbligatorio: In Francia, si visualizzano sempre i prezzi IVA inclusa (Toutes Taxes Comprises) per i consumatori. Gli store forniscono i prezzi localizzati, ma tu devi verificare che la visualizzazione rispetti la legislazione.
  • Menzione "Prezzo IVA inclusa": Il testo deve essere esplicito.
  • Link alle CGV: Le Condizioni Generali di Vendita devono essere accessibili dalla schermata di pagamento.
  • Informazioni sul diritto di recesso: Per gli acquisti digitali, il diritto di recesso si applica a determinate condizioni. Devi informare l'utente.
  • Durata dell'impegno: L'utente deve sapere chiaramente se sottoscrive un abbonamento mensile o annuale e come disdirlo.

Non rispettare queste regole significa rischiare un rifiuto dell'app da parte di Google o una multa dalla DGCCRF. Le CGV e le note legali di TAMSIV sono accessibili dal sito web e dall'app.

Come architettare il PurchaseService in React Native?

Tutto passa attraverso un singleton PurchaseService. Questo è il pattern che uso per tutti i servizi in TAMSIV — ConversationService, CalendarService, GamificationService, tutti seguono lo stesso modello (ne parlo nell'articolo sul refactoring clean architecture).

Il PurchaseService fa 4 cose:

  1. Inizializza RevenueCat all'avvio dell'app con la chiave API del progetto.
  2. Ascolta i cambiamenti di stato: upgrade, downgrade, scadenza, ripristino. Ogni cambiamento innesca un aggiornamento delle feature gates.
  3. Espone il piano attivo tramite un hook: usePurchase() restituisce il piano corrente, la data di scadenza e le funzionalità disponibili.
  4. Sincronizza con Supabase: Il piano attivo è anche memorizzato nel database in modo che il backend possa verificare i permessi (ad esempio, limitare il numero di richieste vocali per il piano Free).

Il ripristino degli acquisti

Questo è il caso più insidioso. Un utente cambia telefono, reinstalla l'app e si aspetta di ritrovare il suo abbonamento Pro. RevenueCat gestisce questo tramite restorePurchases(), ma il tempismo è critico: il ripristino può richiedere alcuni secondi, durante i quali l'utente è in modalità Free. Se non gestisci questo stato intermedio, l'utente vede una schermata "Aggiorna a Pro" mentre è già Pro.

La soluzione: uno stato "caricamento" esplicito all'avvio, che blocca la visualizzazione delle feature gates finché RevenueCat non ha confermato il piano attivo.

Quali metriche seguire per gli abbonamenti?

RevenueCat fornisce un'ottima dashboard. Le metriche che seguo quotidianamente:

  • MRR (Monthly Recurring Revenue): Il ricavo ricorrente mensile. La metrica regina.
  • Conversione Free → Pro: Quale percentuale di utenti gratuiti effettua l'upgrade?
  • Churn rate: Quanti abbonati annullano ogni mese?
  • Trial conversion: Se offri una prova gratuita, quale percentuale converte?
  • Revenue per user: Il ricavo medio per utente attivo.

Queste metriche alimentano la dashboard admin che ho costruito per monitorare la salute del progetto in tempo reale.

Quali errori evitare con RevenueCat?

Ecco le trappole in cui sono caduto:

  • Non testare in sandbox: Google Play ha una modalità sandbox per gli acquisti. Usala sistematicamente. Un bug di pagamento in produzione significa un rimborso + un utente perso.
  • Dimenticare la migrazione degli utenti esistenti: Se aggiungi abbonamenti a un'app esistente, gli utenti attuali devono essere migrati al piano Free correttamente. Nessun downgrade silenzioso.
  • Non gestire la modalità aereo: RevenueCat memorizza nella cache il piano attivo localmente. Ma se l'utente è offline durante un upgrade, le feature gates potrebbero essere desincronizzate. Prevedi un meccanismo di riconciliazione al ritorno online.
  • Ignorare i webhook: RevenueCat invia webhook per ogni evento (acquisto, annullamento, rinnovo). Il backend deve elaborarli per mantenere Supabase sincronizzato.
Pila di documenti legali e contratti su una scrivania di legno, penna e occhiali da lettura
Gli obblighi legali sono importanti quanto il codice stesso.

Come interagisce il sistema di referral con gli abbonamenti?

TAMSIV ha un sistema di referral che offre un mese gratuito di Pro o Team quando un utente invita un amico. Questo aggiunge un livello di complessità: il codice promozionale RevenueCat deve essere applicato correttamente, il mese gratuito deve iniziare al momento giusto e il ritorno al piano originale deve essere trasparente.

L'interazione referral + abbonamento ha generato 3 dei 16 casi limite menzionati sopra. È un ottimo strumento di crescita, ma richiede un'implementazione accurata.

FAQ

Perché RevenueCat piuttosto che l'API nativa Google Play Billing?

L'API nativa Google Play Billing è complessa, mal documentata e cambia regolarmente. RevenueCat astrae questa complessità con un SDK pulito, una dashboard analitica e il supporto multi-piattaforma (Android + iOS). Il costo (gratuito fino a $2.5k MRR, poi l'1% del ricavo) è trascurabile rispetto al tempo di sviluppo risparmiato.

È necessario offrire una prova gratuita?

Dipende dal tuo modello. Per TAMSIV, il piano Free è già una prova permanente delle funzionalità di base. Una prova gratuita del Pro per 7 giorni è in fase di test — i primi dati mostrano un tasso di conversione più elevato, ma anche un churn maggiore dopo la prova.

Come visualizzare i prezzi in più valute?

Google Play fornisce i prezzi localizzati tramite l'API. RevenueCat li espone in offerings.current.availablePackages. Non devi gestire tu la conversione delle valute — lo store visualizza sempre il prezzo locale. In Francia, è in euro IVA inclusa.

Cosa succede se Google Play è inattivo?

RevenueCat memorizza nella cache il piano attivo sul dispositivo. Se Google Play è temporaneamente non disponibile, l'utente mantiene il suo accesso. Il rischio è minimo perché Google Play ha un uptime del 99.99%. Ma il PurchaseService prevede un fallback alla cache locale in caso di timeout dell'API.

Come gestire l'IVA per le vendite internazionali?

È lo store che gestisce l'IVA, non tu. Google Play raccoglie e versa l'IVA in base al paese dell'acquirente. Tu ricevi l'importo netto. Ma devi comunque visualizzare i prezzi IVA inclusa nell'app e rispettare le regole di visualizzazione del paese dell'utente.