Implementare le sostituzioni degli abbonamenti con Fatturazione Google Play

1. Introduzione

Questo codelab ti insegna a utilizzare la Libreria Fatturazione Google Play (PBL) per gestire le modifiche ai piani di abbonamento. Scoprirai in che modo le varie modalità di sostituzione influiscono sui prezzi e sui diritti degli utenti, imparando a elaborare le notifiche in tempo reale per lo sviluppatore (RTDN) di backend.

Pubblico

Progettato per gli sviluppatori di app per Android, questo codelab fornisce indicazioni sull'implementazione di funzionalità avanzate di gestione degli abbonamenti. Le linee guida ti aiutano a offrire agli utenti un'esperienza fluida per eseguire l'upgrade, il downgrade o la transizione tra diversi piani di abbonamento.

Cosa imparerai…

  • Come creare abbonamenti in Play Console.
  • Come scegliere il ReplacementMode corretto (ad es. WITH_TIME_PRORATION anziché DEFERRED) in base alle norme di upgrade e downgrade dell'app.
  • Come configurare BillingFlowParams all'interno di launchBillingFlow per attivare il flusso di acquisto di Google Play per la sostituzione di un piano.
  • Come utilizzare le notifiche in tempo reale per lo sviluppatore (RTDN) e l'API purchases.subscriptionsv2 per revocare in modo sicuro il vecchio accesso e concedere un nuovo accesso al backend

Che cosa ti serve

2. Crea l'app di esempio

Questo codelab utilizza un'app per Android di esempio per mostrare come implementare le sostituzioni degli abbonamenti nella libreria PBL. L'app di esempio è progettata per essere un'app per Android completamente funzionante con il codice sorgente completo che mostra i seguenti aspetti:

  • Integrazione dell'app con PBL
  • Implementare le sostituzioni degli abbonamenti

Se hai già familiarità con le sostituzioni degli abbonamenti e PBL, puoi scaricare l'app di esempio e provarla.

Il seguente video dimostrativo mostra l'aspetto e il comportamento dell'app di esempio dopo la distribuzione e l'esecuzione.

Prerequisiti

Prima di creare ed eseguire il deployment dell'app di esempio:

Build

Per creare l'app di esempio come richiesto per seguire il codelab:

  1. Scarica l'app di esempio da GitHub.
  2. Aggiorna applicationId all'interno di build.gradle dell'app di esempio in modo che rifletta l'ID applicazione della tua app in Play Console.
  3. Crea l'app di esempio.
    Nota: in questo modo l'app viene creata correttamente per i test locali. Tuttavia, l'esecuzione dell'app non recupera prodotti e prezzi perché gli abbonamenti richiesti non sono ancora stati creati in Play Console. La sezione successiva tratterà la creazione di abbonamenti in Developer Console.

3. Creare abbonamenti in Play Console

Il sistema di abbonamenti di Google Play consente di creare, gestire e vendere abbonamenti in modo flessibile. In Play Console puoi configurare abbonamenti con più piani base, ciascuno contenente più offerte. Le offerte di abbonamento possono avere vari modelli di prezzi e opzioni di idoneità. Per questo codelab, creerai tre abbonamenti: piano Premium, Basic Plan e Lite Plan, simulando una tipica offerta di abbonamenti a vari prezzi. Ciascuno di questi avrà un singolo piano base mensile ricorrente.

Crea una nuova sottoscrizione

Per creare un nuovo abbonamento

  1. Apri Play Console e vai alla pagina Abbonamenti (Monetizza con Google Play > Prodotti > Abbonamenti).
  2. Fai clic su Crea sottoscrizione.
  3. Inserisci i dettagli dell'abbonamento:
    • ProductID : inserisci un ID prodotto univoco. Inserisci premium_plan.
    • Nome : inserisci un nome breve per l'abbonamento. Esempio: Premium Plan.
  4. Fai clic su Crea.

Crea il piano base

  1. Apri Play Console e vai alla pagina Abbonamenti (Monetizza con Google Play > Prodotti > Abbonamenti).
  2. Fai clic sulla Freccia destra accanto all'abbonamento in cui vuoi creare un piano base per visualizzare i relativi dettagli.
  3. Fai clic su Aggiungi piano base.
  4. Inserisci un ID piano base. Esempio monthly-auto-renewing.
  5. Scegli Rinnovo automatico come tipo.
  6. Per il piano base con rinnovo automatico, imposta quanto segue:
    • Periodo di fatturazione: mensile.
    • Periodo di tolleranza: 7 giorni.
    • Modifiche relative a piano di fatturazione e offerta: Addebito alla data di fatturazione.
    • Riabbonati: Consenti.
  7. Nella sezione Prezzo e disponibilità, fai clic su Imposta prezzi per impostare il prezzo del piano base.
  8. Seleziona tutti i paesi e le regioni, quindi fai clic su Imposta prezzo.
  9. Imposta il prezzo su 10$ per questo piano base e fai clic su Aggiorna.
  10. Una volta impostato il prezzo del piano base, fai clic su Salva e poi su Attiva in basso a destra.

Crea abbonamenti per l'app di esempio

Ai fini di questo codelab, crea due abbonamenti aggiuntivi con la seguente configurazione:

  • Piano base
    • ID prodotto: basic_plan
    • Nome: Basic Plan
    • ID piano base: monthly-auto-renewing
    • Prezzo: 5$
  • Piano Lite
    • ID prodotto: lite_plan
    • Nome: Piano Lite
    • ID piano base: monthly-auto-renewing
    • Prezzo: 3$

L'app di esempio è configurata per utilizzare questi ID prodotto e ID piano base. Puoi creare abbonamenti diversi con configurazioni diverse. In questo caso, dovrai modificare l'app di esempio per utilizzare l'ID prodotto che hai creato.

Video sulla creazione di abbonamenti

Il seguente video mostra i passaggi descritti in precedenza per creare un abbonamento in Play Console.

4. Sostituzioni di abbonamenti

Gli sviluppatori che eseguono l'integrazione con PBL possono offrire agli abbonati esistenti varie opzioni per modificare il piano di abbonamento in modo da soddisfare meglio le loro esigenze:

  • Se vendi più livelli di abbonamento, ad esempio abbonamenti base e premium, puoi consentire agli utenti di cambiare livello acquistando un piano base o un'offerta di un altro abbonamento.
  • Puoi consentire agli utenti di modificare il periodo di fatturazione corrente, ad esempio passando da un piano mensile a un piano annuale.
  • Puoi anche consentire agli utenti di passare dai piani con rinnovo automatico a quelli prepagati e viceversa.

Quando gli utenti decidono di eseguire l'upgrade, il downgrade o modificare l'abbonamento, devi specificare una modalità di sostituzione che determina come viene applicato il valore proporzionale del periodo di fatturazione corrente e quando si verifica la modifica dei diritti per gli utenti.

Libreria Fatturazione Play offre diverse opzioni ReplacementMode per controllare questo comportamento.

Modalità di sostituzione disponibili

  • WITH_TIME_PRORATION: l'upgrade o il downgrade dell'elemento dell'abbonamento viene eseguito immediatamente. Il tempo rimanente viene adeguato in base alla differenza di prezzo e accreditato al nuovo abbonamento aggiornando la data di fatturazione successiva. Questo è il comportamento predefinito.
  • CHARGE_PRORATED_PRICE: l'elemento dell'abbonamento viene eseguito immediatamente e il ciclo di fatturazione rimane invariato. All'utente viene quindi addebitata la differenza di prezzo per il periodo rimanente.
  • CHARGE_FULL_PRICE: l'elemento dell'abbonamento viene eseguito l'upgrade o il downgrade immediatamente e all'utente viene addebitato immediatamente il prezzo intero per questo nuovo diritto. Il valore rimanente dell'abbonamento precedente viene riportato per lo stesso diritto o ripartito proporzionalmente per il periodo di tempo in caso di passaggio a un diritto diverso.
  • WITHOUT_PRORATION: l'upgrade o il downgrade dell'elemento dell'abbonamento viene eseguito immediatamente e il nuovo prezzo viene addebitato al momento del rinnovo dell'abbonamento. Il ciclo di fatturazione rimane invariato.
  • DEFERRED: l'upgrade o il downgrade dell'articolo dell'abbonamento viene eseguito solo al momento del rinnovo dell'abbonamento.

5. WITH_TIME_PRORATION

In questa modalità di sostituzione, viene eseguito immediatamente l'upgrade o il downgrade dell'articolo dell'abbonamento. Il tempo rimanente viene adeguato in base alla differenza di prezzo e accreditato al nuovo abbonamento posticipando la data di fatturazione successiva. Questo è il comportamento predefinito.

Scenario di esempio

Un utente passa da un piano Basic (4,99 $ al mese) a un piano Premium (9,99 $ al mese) il 15 aprile, a metà del ciclo di fatturazione mensile.

In questo caso:

  • L'utente ottiene immediatamente l'accesso al piano Premium.
  • Google Play calcola automaticamente il periodo di ripartizione proporzionale. Supponiamo che Play calcoli che i 15 giorni rimanenti del piano Basic valgano 7 giorni del piano Premium, la data di fatturazione successiva viene anticipata al 21 aprile.
  • Non è richiesto alcun pagamento immediato da parte dell'utente.

Snippet di codice

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

Esegui l'upgrade con WITH_TIME_PRORATION

Per simulare questo scenario:

  • In MainActivity dell'app di esempio, aggiorna replacementMode nello snippet di codice a SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION.
  • Ricrea e avvia l'applicazione.
  • Annulla gli abbonamenti esistenti (se presenti) dal Google Play Store e attendi la loro scadenza.
  • Acquista il piano Basic.
  • Passa al piano Premium.

Il diritto dell'utente viene immediatamente aggiornato al piano Premium. L'importo da pagare immediatamente dall'utente è pari a 0,00 $. Il valore rimanente del piano Basic viene ripartito in tempo per il piano Premium, il che anticipa la data di rinnovo successiva. All'utente verrà addebitato l'importo del rinnovo di 9,99 $alla nuova data di fatturazione modificata.

Esegui il downgrade con WITH_TIME_PRORATION

Per simulare questo scenario:

  • In MainActivity dell'app di esempio, aggiorna replacementMode nello snippet di codice a SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION.
  • Ricrea e avvia l'applicazione.
  • Annulla gli abbonamenti esistenti (se presenti) dal Google Play Store e attendi la loro scadenza.
  • Acquista il piano Basic.
  • Passa al piano Lite.

I diritti dell'utente vengono immediatamente declassati al piano Lite. L'importo da pagare immediatamente è pari a 0 $. Il valore rimanente del piano Basic viene ripartito in tempo per il piano Lite, il che estende notevolmente la data di rinnovo successiva. All'utente verrà addebitato l'importo del rinnovo di 2,99 $alla nuova data di fatturazione modificata.

Conclusione

In questa sezione hai imparato come WITH_TIME_PRORATION modifica i diritti degli utenti senza addebiti immediati, regolando il tempo che intercorre fino al successivo rinnovo in base alla differenza di prezzo. È una strategia predefinita efficace per eseguire l'upgrade o il downgrade degli utenti.

6. CHARGE_PRORATED_PRICE

In questa modalità di sostituzione, l'elemento dell'abbonamento viene eseguito l'upgrade immediatamente e il ciclo di fatturazione rimane invariato. All'utente viene quindi addebitata la differenza di prezzo per il periodo rimanente.

Nota: questa opzione è disponibile solo per l'upgrade di un articolo in abbonamento, in cui il prezzo per unità di tempo aumenta.

Scenario di esempio

Un utente con il piano Basic (4,99 $ al mese) decide di eseguire l'upgrade al piano Premium (9,99 $ al mese) il 20 aprile, quando mancano circa 10 giorni al termine del ciclo di fatturazione mensile.

In questo caso:

  • L'utente ottiene immediatamente l'accesso al piano Premium.
  • All'utente viene immediatamente addebitata la differenza proporzionale per i 10 giorni rimanenti del ciclo di fatturazione corrente. Si tratta di circa 2,99 $, che rappresentano 10 giorni di piano Premium.
  • La data di fatturazione per l'utente non cambia.

Snippet di codice

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

Esegui l'upgrade con CHARGE_PRORATED_PRICE

Per simulare questo scenario:

  • In MainActivity dell'app di esempio, aggiorna replacementMode nello snippet di codice a SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE.
  • Ricrea e avvia l'applicazione.
  • Annulla gli abbonamenti esistenti (se presenti) dal Google Play Store e attendi la loro scadenza.
  • Acquista il piano Basic.
  • Passa al piano Premium.

L'utente esegue immediatamente l'upgrade al piano Premium, mantenendo la data di rinnovo originale. L'importo da pagare immediatamente è la differenza proporzionale tra i prezzi dei piani Premium e Base per i giorni rimanenti del ciclo corrente. Alla data di rinnovo, all'utente verrà addebitato l'intero importo del rinnovo di Premium pari a 9,99 $.

Esegui il downgrade con CHARGE_PRORATED_PRICE

Per simulare questo scenario:

  • In MainActivity dell'app di esempio, aggiorna replacementMode nello snippet di codice a SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE.
  • Ricrea e avvia l'applicazione.
  • Annulla gli abbonamenti esistenti (se presenti) dal Google Play Store e attendi la loro scadenza.
  • Acquista il piano Basic.
  • Passa al piano Lite.

Questa modalità di sostituzione genera un errore durante un downgrade perché è disponibile solo per gli upgrade degli elementi di abbonamento in cui il prezzo per unità di tempo aumenta. Il flusso di fatturazione non andrà a buon fine e verrà visualizzato un errore che indica all'utente che la modalità di ripartizione proporzionale non è supportata per i downgrade.

Conclusione

Questa sezione spiegava come CHARGE_PRORATED_PRICE consente upgrade immediati addebitando agli utenti l'esatta differenza di prezzo per il periodo di fatturazione rimanente, lasciando intatto il ciclo di fatturazione. Questa opzione è utile quando l'utente vuole eseguire l'upgrade a un livello più costoso senza modificare la data di fatturazione.

7. CHARGE_FULL_PRICE

In questa modalità di sostituzione, l'elemento dell'abbonamento viene eseguito l'upgrade o il downgrade immediatamente e all'utente viene addebitato immediatamente il prezzo intero del nuovo diritto. Il valore rimanente dell'abbonamento precedente viene riportato per lo stesso diritto o ripartito proporzionalmente per il periodo di tempo in caso di passaggio a un diritto diverso.

Scenario di esempio

Un utente ha sottoscritto il piano Basic (4,99 $ al mese a partire dal 1° aprile). Il 20 aprile l'utente vuole passare al piano Premium (9, 99 $ al mese).

In questo caso:

  • All'utente viene immediatamente addebitato il prezzo intero del piano Premium (9,99 $).
  • Il valore rimanente del piano Basic (ad es. 10 giorni) viene convertito in tempo equivalente per il piano Premium. In questo esempio, 10 giorni di base equivalgono a 5 giorni di Premium.
  • La successiva data di rinnovo dell'utente viene modificata in modo da includere questo periodo proporzionale. Pertanto, la data di rinnovo diventa il 25 maggio (20 aprile + 1 mese + 5 giorni).
  • I rinnovi successivi avverranno mensilmente a partire dal 25 maggio.

Snippet di codice

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

Esegui l'upgrade con CHARGE_FULL_PRICE

Per simulare questo scenario:

  • In MainActivity dell'app di esempio, aggiorna replacementMode nello snippet di codice a SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE.
  • Ricrea e avvia l'applicazione.
  • Annulla gli abbonamenti esistenti (se presenti) dal Google Play Store e attendi la loro scadenza.
  • Acquista il piano Basic.
  • Passa al piano Premium.

L'utente esegue immediatamente l'upgrade al piano Premium. L'importo da pagare immediatamente è il prezzo intero del piano Premium: 9,99 $. L'eventuale valore rimanente del piano Basic viene convertito in tempo sul nuovo piano Premium, posticipando leggermente la prima data di rinnovo. Successivamente, l'importo del rinnovo sarà di 9,99 € per ciclo.

Downgrade con CHARGE_FULL_PRICE

Per simulare questo scenario:

  • In MainActivity dell'app di esempio, aggiorna replacementMode nello snippet di codice a SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE.
  • Ricrea e avvia l'applicazione.
  • Annulla gli abbonamenti esistenti (se presenti) dal Google Play Store e attendi la loro scadenza.
  • Acquista il piano Basic.
  • Passa al piano Lite.

L'utente esegue il downgrade al piano Lite immediatamente e viene avviato un nuovo ciclo di fatturazione. L'importo da pagare immediatamente è il prezzo target completo di 2,99 $. La parte non utilizzata del piano Basic viene ripartita in tempo per il nuovo piano Lite, estendendo la prima data di rinnovo. Dopodiché, l'importo del rinnovo sarà di 2,99 € per ciclo.

Conclusione

In questa sezione abbiamo illustrato come CHARGE_FULL_PRICE addebiti all'utente l'intero importo il giorno del cambio, avviando immediatamente un nuovo ciclo di fatturazione. L'eventuale saldo rimanente del piano precedente viene applicato in modo lineare alla successiva data di rinnovo.

8. WITHOUT_PRORATION

In questa modalità di sostituzione, l'elemento dell'abbonamento viene sottoposto immediatamente a upgrade o downgrade e il nuovo prezzo viene addebitato al momento del rinnovo dell'abbonamento.

Scenario di esempio

Un utente ha sottoscritto il piano Basic (4,99 $ al mese a partire dal 1° aprile). Il 20 aprile l'utente vuole passare al piano Premium (9, 99 $ al mese).

In questo caso:

  • L'utente ottiene immediatamente l'accesso al piano Premium.
  • L'utente non dovrà pagare il prezzo più elevato di 9,99 $fino alla successiva data di rinnovo dell'abbonamento (1° maggio).

Snippet di codice

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

Esegui l'upgrade con WITHOUT_PRORATION

Per simulare questo scenario:

  • In MainActivity dell'app di esempio, aggiorna replacementMode nello snippet di codice a SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION.
  • Ricrea e avvia l'applicazione.
  • Annulla gli abbonamenti esistenti (se presenti) dal Google Play Store e attendi la loro scadenza.
  • Acquista il piano Basic.
  • Passa al piano Premium.

L'utente esegue immediatamente l'upgrade al piano Premium, mantenendo la data di rinnovo esistente. L'importo da pagare immediatamente è pari a 0 $. L'utente ha accesso al piano Premium per il tempo rimanente del ciclo in questione, prima di passare al nuovo importo di rinnovo di 9,99 $alla successiva data di fatturazione.

Downgrade con WITHOUT_PRORATION

Per simulare questo scenario:

  • In MainActivity dell'app di esempio, aggiorna replacementMode nello snippet di codice a SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION.
  • Ricrea e avvia l'applicazione.
  • Annulla gli abbonamenti esistenti (se presenti) dal Google Play Store e attendi la loro scadenza.
  • Acquista il piano Basic.
  • Passa al piano Lite.

L'utente esegue immediatamente il downgrade al piano Lite, perdendo le funzionalità Basic per cui ha pagato. L'importo da pagare immediatamente è pari a 0 $. Il ciclo di fatturazione continua senza modifiche e l'utente pagherà la nuova tariffa più bassa di 2,99 $al successivo rinnovo programmato.

Conclusione

Questa sezione ha mostrato come WITHOUT_PRORATION scambia immediatamente i diritti dell'utente senza addebitare costi di pagamento, lasciando invariato il ciclo di fatturazione.

9. DIFFERITO

In questa modalità di sostituzione, l'elemento dell'abbonamento viene sottoposto a upgrade o downgrade solo al momento del rinnovo, ma il nuovo acquisto viene emesso immediatamente. L'elemento esistente è impostato su non rinnovabile e scade alla fine del ciclo di fatturazione corrente, mentre il diritto appena richiesto inizia subito dopo.

Scenario di esempio

Un utente ha sottoscritto il piano Basic (4,99 $ al mese a partire dal 1° aprile). Il 20 aprile l'utente vuole passare al piano Premium (9, 99 $ al mese).

In questo caso:

  • L'utente non incorre in alcun addebito immediato.
  • L'utente continua a ricevere le funzionalità di Basic fino alla fine del ciclo di fatturazione corrente (30 aprile).
  • Il piano di abbonamento verrà automaticamente eseguito l'upgrade a Premium alla successiva data di rinnovo (1° maggio).

Snippet di codice

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.DEFERRED;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

Upgrade con DEFERRED

Per simulare questo scenario:

  • In MainActivity dell'app di esempio, aggiorna replacementMode nello snippet di codice a SubscriptionProductReplacementParams.ReplacementMode.DEFERRED.
  • Ricrea e avvia l'applicazione.
  • Annulla gli abbonamenti esistenti (se presenti) dal Google Play Store e attendi la loro scadenza.
  • Acquista il piano Basic.
  • Passa al piano Premium.

L'utente rimarrà con il piano Basic fino alla fine del ciclo di fatturazione corrente. L'importo da pagare immediatamente è pari a 0 $. Alla data di rinnovo, i suoi diritti vengono aggiornati al piano Premium e gli verrà addebitato il nuovo importo di rinnovo di 9,99 $.

Downgrade con DEFERRED

Per simulare questo scenario:

  • In MainActivity dell'app di esempio, aggiorna replacementMode nello snippet di codice a SubscriptionProductReplacementParams.ReplacementMode.DEFERRED.
  • Ricrea e avvia l'applicazione.
  • Annulla gli abbonamenti esistenti (se presenti) dal Google Play Store e attendi la loro scadenza.
  • Acquista il piano Basic.
  • Passa al piano Lite.

L'utente rimarrà con il piano Basic fino alla fine del ciclo di fatturazione corrente. L'importo da pagare immediatamente è pari a 0 $. Alla data di rinnovo, il loro diritto viene aggiornato al piano Lite e verrà addebitato il nuovo importo di rinnovo di 2,99 $.

Conclusione

Questa sezione ha spiegato come la modalità di sostituzione DEFERRED posticipa un upgrade o un downgrade fino alla fine del periodo a pagamento di un utente attivo. Ciò lo rende particolarmente ideale per i downgrade, in quanto impedisce la perdita delle funzionalità già acquistate.

10. Elaborazione backend e lato client

Dopo che un utente ha attivato una sostituzione dell'abbonamento riuscita, assicurati che sia la tua app sia il backend gestiscano correttamente la modifica per evitare problemi come interruzioni del servizio o doppia fatturazione.

Scenario di esempio

  • L'utente ha un piano mensile Basic (product_id basic_plan e purchase_token basic_purchase_token_123).
  • L'utente passa a un piano Premium utilizzando una modalità di sostituzione immediata (una tra WITHOUT_PRORATION, WITH_TIME_PRORATION, CHARGE_PRORATED_PRICE, CHARGE_FULL_PRICE)
  • Una volta completato il cambio di abbonamento, Google lo considera un NUOVO abbonamento e crea un token di acquisto nuovo e diverso per il piano Premium (product_id premium_plan e purchase_token premium_purchase_token_123).

Elaborazione lato client

onPurchasesUpdated

Al termine dell'acquisto sostitutivo, viene attivato PurchasesUpdatedListener. Anche se si tratta di un cambio, Google Play considera il piano Premium come un nuovo acquisto.

L'app riceverà un oggetto Purchase contenente il token di acquisto premium_purchase_token_123 e l'ID prodotto premium_plan. Devi trattarlo esattamente come un nuovo abbonato: verifica il token e preparati a concedere l'accesso.

@Override
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
    if (billingResult.getResponseCode() == BillingResponseCode.OK && purchases != null) {
        for (Purchase purchase : purchases) {
            // purchase.getPurchaseToken() = premium_purchase_token_123
            // purchase.getProducts() will contain premium_plan
            // Verify the purchase and grant entitlement
            handleNewPurchase(purchase);
        }
    }
}

queryPurchasesAsync

queryPurchasesAsync restituisce solo gli abbonamenti attivi acquistati dalla tua app. Devi fare affidamento su questo metodo per determinare quale diritto mostrare all'utente. Per le sostituzioni immediate, queryPurchasesAsync() interromperà la restituzione del vecchio token di acquisto BASIC e restituirà solo il nuovo token di acquisto PREMIUM.

Chiama questo metodo ogni volta che la tua app riprende o un acquisto viene completato. Se è presente il token Premium, concedi immediatamente le funzionalità Premium e rimuovi quelle di base.

Elaborazione backend (RTDN)

Quando si verifica una sostituzione, Google Play invia una notifica in tempo reale per lo sviluppatore (RTDN) all'argomento Pub/Sub configurato.

  • In caso di sostituzione immediata, Google invia una notifica in tempo reale (RTDN) SUBSCRIPTION_PURCHASED con il nuovo token di acquisto.Payload RTDN di esempio
    {
      "version":"1.0",
      "packageName":"com.google.play.billing.samples.subscriptions",
      "eventTimeMillis":"...",
      "subscriptionNotification":
      {
        "version":"1.0",
        "notificationType":4, // SUBSCRIPTION_PURCHASED
        "purchaseToken":"premium_purchase_token_123" //purchase token for the new subscription
      }
    }
    
  • Quando il server riceve il nuovo token di acquisto da RTDN, chiama l'API purchases.subscriptionsV2 con il nuovo token di acquisto per recuperare i dettagli dell'acquisto. La risposta dell'API contiene un campo linkedPurchaseToken utilizzato per determinare se il token di acquisto si riferisce a un nuovo acquisto di abbonamento o a una sostituzione dell'abbonamento.
  • Nel caso di una sostituzione dell'abbonamento, linkedPurchaseToken si riferisce al token di acquisto del vecchio abbonamento. In questo scenario, sarebbe basic_purchase_token_123.Esempio di risposta GET purchases.subscriptionsV2
    curl \
    'https://androidpublisher.googleapis.com/androidpublisher/v3/applications/<application_id>/purchases/subscriptionsv2/tokens/premium_purchase_token_123' \
    --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
    --header 'Accept: application/json'
    
    {
      "kind": "androidpublisher#subscriptionPurchaseV2",
      "startTime": "...",
      "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
      "latestOrderId": "GPA.<order_id>",
      "linkedPurchaseToken": "basic_purchase_token_123", // The purchase token of the subscription that was replaced (Basic Plan in this case)
      "acknowledgementState": "ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED",
      "lineItems": [
        {
          "productId": "premium_plan", // productID of the new subscription (Premium Plan in this case)
          "expiryTime": "....",
          "autoRenewingPlan": {...},
          "offerDetails": {
            "basePlanId": "monthly-auto-renewing" // base plan ID of the new subscription
          },
          "itemReplacement": { // Details about the subscription replacement
            "productId": "subscription_basic", // productID of the old subscription (Basic Plan in this case)
            "replacementMode": "CHARGE_PRORATED_PRICE", // Replacement strategy used for this subscription change
            "basePlanId": "monthly-auto-renewing" // base plan ID of the old subscription 
          },
          "offerPhase": {...}
        }
      ],
      "etag": "<etag_value>"
    }
    
  • Devi confermare il nuovo acquisto di Premium. Puoi farlo all'interno dell'app o nel backend. Se non confermi l'acquisto entro 3 giorni, riceverai un rimborso e la revoca del diritto. Per maggiori dettagli sull'elaborazione e la conferma degli acquisti, consulta la documentazione per gli sviluppatori.

Conclusione

Questa sezione ha trattato i passaggi per la gestione delle sostituzioni immediate degli abbonamenti sia sul client sia sul backend. Hai scoperto che Google Play considera il nuovo piano come un acquisto nuovo di zecca, emettendo un nuovo token di acquisto. Sul client, devi elaborare questo nuovo acquisto utilizzando PurchasesUpdatedListener e aggiornare i diritti in base alla risposta di queryPurchasesAsync. Nel backend, devi ascoltare le notifiche in tempo reale (RTDN) SUBSCRIPTION_PURCHASED per il nuovo token, utilizzare l'API purchases.subscriptionsv2 per identificare linkedPurchaseToken del vecchio abbonamento e revocare tempestivamente l'accesso associato al vecchio token concedendo il nuovo diritto. Ricordati di confermare sempre il nuovo acquisto.

11. Elabora sostituzioni posticipate

A differenza delle modalità di sostituzione immediata, ReplacementMode.DEFERRED posticipa la modifica dell'abbonamento e l'aggiornamento dei diritti alla fine del ciclo di fatturazione corrente. La gestione delle sostituzioni differite richiede una logica specifica per garantire che gli utenti ricevano il diritto corretto al momento opportuno.

Scenario di esempio

  • L'utente ha un piano mensile Basic (product_id basic_plan e purchase_token basic_purchase_token_123) che si rinnova il 15 aprile.
  • Il 1° aprile, l'utente decide di passare a un piano Premium utilizzando ReplacementMode.DEFERRED.
  • Google crea immediatamente un NUOVO token di acquisto per il piano Premium (product_id premium_plan e purchase_token premium_purchase_123), ma l'importo da addebitare all'utente e il diritto sono pianificati per il giorno 15 aprile.

Elabora sostituzione differita

1. Subito dopo il completamento del flusso di acquisto (app)

  • PurchasesUpdatedListener viene richiamato al termine del flusso di acquisto. L'app riceverà un oggetto Purchase contenente il nuovo token di acquisto premium_purchase_token_123, ma l'ID prodotto farà comunque riferimento al vecchio basic_plan poiché l'utente ha diritto solo al piano Basic. Devi trattarlo esattamente come un nuovo acquisto e riconoscere il token.
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
        if (billingResult.getResponseCode() == BillingResponseCode.OK && purchases != null) {
            for (Purchase purchase : purchases) {
                // purchase.getPurchaseToken() = premium_purchase_token_123
                // purchase.getProducts() will contain basic_plan
                // Verify and acknowledge the purchase
                handleNewPurchase(purchase);
            }
        }
    }
    
  • queryPurchasesAsync restituisce immediatamente l'acquisto con il nuovo token di acquisto (premium_purchase_token_123) e il diritto originale (basic_plan) associato. Puoi fare affidamento su questo per continuare a concedere all'utente il diritto al piano Basic.

2. Subito dopo il completamento del flusso di acquisto (backend)

  • La notifica in tempo reale per lo sviluppatore SUBSCRIPTION_PURCHASED viene inviata immediatamente dopo il flusso di acquisto per il nuovo token di acquisto (premium_purchase_token_123).Esempio di payload RTDN
    {
      "version":"1.0",
      "packageName":"com.google.play.billing.samples.subscriptions",
      "eventTimeMillis":"...",
      "subscriptionNotification":
      {
        "version":"1.0",
        "notificationType":4, // SUBSCRIPTION_PURCHASED
        "purchaseToken":"premium_purchase_token_123" //purchase token for the new subscription
      }
    }
    
  • Chiama GET purchases.subscriptionsv2 con il nuovo token di acquisto per recuperare i dettagli dell'acquisto. La risposta contiene due elementi pubblicitari.
    • Una che rappresenta il vecchio abbonamento (piano base) e ha una expiryTime futura. Il vecchio abbonamento non verrà rinnovato e ha un deferredItemReplacement contenente il nuovo abbonamento (piano premium). Ciò indica una sostituzione in attesa del vecchio diritto alla scadenza.
    • Uno che rappresenta l'abbonamento appena acquistato. Non è impostato alcun valore per "expiryTime"
    Risposta API di esempio
    {
      "kind": "androidpublisher#subscriptionPurchaseV2",
      "startTime": "2026-05-07T15:50:11.383Z",
      "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
      "latestOrderId": "GPA.<order_id>",
      "linkedPurchaseToken": "basic_purchase_token_123",
      "acknowledgementState": "ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED",
      "lineItems": [
        {
          "productId": "premium_plan", // Premium Plan has no expiry time
          "autoRenewingPlan": {...},
          "offerDetails": {...},
          "itemReplacement": {. // Subscription replacement details
            "productId": "basic_plan",
            "replacementMode": "DEFERRED",
            "basePlanId": "monthly-auto-renewing"
          },
          "offerPhase": {}
        },
        {
          "productId": "basic_plan", // Subscription to be replaced
          "expiryTime": "2026-05-07T15:54:34.768Z", // Expiry time in the future
          "autoRenewingPlan": {},
          "offerDetails": {...},
          "deferredItemReplacement": { // identifier indicating this subscription will be replaced upon renewal
            "productId": "subscription_premium"
          },
          "latestSuccessfulOrderId": "GPA.<order_id>",
          "itemReplacement": {...},
        }
      ],
      "etag": "<etag>"
    }
    
  • Devi confermare il nuovo token di acquisto. Puoi farlo all'interno dell'app o nel backend. Per maggiori dettagli sull'elaborazione e la conferma degli acquisti, consulta la documentazione per gli sviluppatori.
  • La notifica in tempo reale per lo sviluppatore SUBSCRIPTION_EXPIRED viene inviata per il vecchio token di acquisto (basic_purchase_token_123).Payload di esempio della notifica in tempo reale per lo sviluppatore
    {
      "version":"1.0",
      "packageName":"com.google.play.billing.samples.subscriptions",
      "eventTimeMillis":"...",
      "subscriptionNotification":
      {
        "version":"1.0",
        "notificationType":13, // SUBSCRIPTION_EXPIRED
        "purchaseToken":"basic_purchase_token_123" //purchase token for the old subscription
      }
    }
    
  • Quando chiami l'API GET purchases.subscriptionsv2 con il vecchio token di acquisto, questo viene visualizzato come scaduto (SUBSCRIPTION_STATE_EXPIRED). Il diritto per il vecchio piano viene trasferito al nuovo acquisto per il tempo rimanente.

3. Alla data di sostituzione: primo rinnovo dopo il flusso di acquisto (app)

  • queryPurchasesAsync restituisce l'acquisto con il nuovo token di acquisto (premium_purchase_token_123) e il nuovo abbonamento associato (premium_plan).
  • Il nuovo acquisto dovrebbe essere già stato elaborato quando il flusso di acquisto è andato a buon fine, non devi intraprendere alcuna azione speciale, se non assicurarti che all'utente venga concesso l'accesso all'abbonamento corretto.

4. Alla data di sostituzione: primo rinnovo dopo il flusso di acquisto (backend)

  • Con ReplacementMode.DEFERRED, i primi rinnovi seguono il comportamento standard di qualsiasi altro rinnovo che elabora le notifiche in tempo reale SUBSCRIPTION_RENEWED. In questi casi, non è necessario disporre di una logica speciale per le sostituzioni.
  • Chiama GET purchases.subscriptionsv2 con il nuovo token di acquisto per recuperare i dettagli dell'acquisto. La risposta contiene due elementi pubblicitari.
    • Uno che rappresenta il vecchio abbonamento (piano base) e ha una data di scadenza expiryTime nel passato. La vecchia iscrizione non avrà più un valore impostato per il campo deferredItemReplacement.
    • Una che rappresenta il nuovo abbonamento con un expiryTime futuro e il campo autoRenewEnabled impostato su true.
    Risposta API di esempio
    {
      "kind": "androidpublisher#subscriptionPurchaseV2",
      "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
      "latestOrderId": "GPA.<order_id>..0",
      "linkedPurchaseToken": "basic_purchase_token_123", // purchase token of the old subscription
      "acknowledgementState": "ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED",
      "lineItems": [
        {
          "productId": "premium_plan", // New subscription
          "expiryTime": "2026-05-07T16:00:09.437Z", // Expiry time set in the future
          "autoRenewingPlan": {
            "autoRenewEnabled": true, // Auto Renewing Flag set to True
            "recurringPrice": {...}
          },
          "offerDetails": {...},
          "latestSuccessfulOrderId": "GPA.<order_id>..0",
          "itemReplacement": {. // Details of the subscription replacement
            "productId": "basic_plan",
            "replacementMode": "DEFERRED",
            "basePlanId": "monthly-auto-renewing"
          },
          "offerPhase": {...}
        },
        {
          "productId": "basic_plan", // Old subscription, Does not contains the deferredItemReplacement field
          "expiryTime": "2026-05-07T15:54:34.768Z", // Expiry time set in the past
          "autoRenewingPlan": {},
          "offerDetails": {...},
          "latestSuccessfulOrderId": "GPA.<order_id>..0",
          "itemReplacement": {...},
        }
      ],
      "etag": "<etag>"
    }
    

Conclusione

Questa sezione descriveva la gestione univoca richiesta per ReplacementMode.DEFERRED. Hai scoperto che, a differenza delle modalità immediate, la modifica dei diritti avviene solo alla fine del ciclo di fatturazione corrente. Questa sezione ha trattato i passaggi necessari sia per l'app che per il backend per elaborare correttamente l'acquisto iniziale, riconoscere il nuovo token e gestire il cambio di diritto quando il vecchio abbonamento scade e il nuovo diventa attivo.

12. Sandbox per la sostituzione degli abbonamenti

La funzionalità Replacement Playground nell'app di esempio ti consente di testare gli upgrade e i downgrade degli abbonamenti per i prodotti in abbonamento configurati nel tuo account Google Play Console. Questa sezione descrive come utilizzare la funzionalità Sandbox di sostituzione.

Configurazione

Per utilizzare la funzionalità Playground sostitutivo, assicurati di:

  • Il packageId nel file build.gradle corrisponde all'applicazione configurata in Google Play Console.
  • Il tuo account utente di test è registrato come tester delle licenze in Google Play Console. Per scoprire di più sui test delle licenze, consulta Testare l'implementazione della fatturazione della tua app.

Sandbox per la sostituzione degli abbonamenti

L'app di esempio include una scheda Replacement Playground, che ti consente di simulare le modifiche agli abbonamenti. Puoi eseguire query sugli abbonamenti definiti in Play Console e testare il passaggio da uno all'altro utilizzando varie modalità di sostituzione. Questo ambiente di prova ti aiuta a capire in che modo le diverse modalità influiscono sui cicli di fatturazione e sui diritti per i tuoi abbonamenti, in modo da poter determinare quali opzioni si adattano meglio alle esigenze della tua attività.

Per simulare le sostituzioni utilizzando il playground:

  1. Vai alla scheda Playground.
  2. Se hai un abbonamento attivo, questo verrà evidenziato. Questo è l'abbonamento che verrà sostituito.

Home page di Playground

  1. Se non hai un abbonamento attivo:devi prima acquistarne uno.
    • Per impostazione predefinita, Playground elenca i piani Basic, Premium e Lite creati per questo codelab.
    • Per eseguire test con altri piani configurati in Play Console, fai clic su Aggiungi piano personalizzato, cerca per productId e basePlanId.
    • Acquista l'abbonamento selezionato.
    • Ora dovrebbe essere visualizzato l'abbonamento attivo appena acquistato dall'utente. Aggiungi abbonamento personalizzato
  2. Seleziona l'abbonamento di destinazione a cui l'utente vuole passare .
  3. Seleziona una Modalità di sostituzione per la transizione.

Seleziona la modalità di sostituzione

  1. Fai clic sul pulsante Testa sostituzione per simulare la sostituzione dell'abbonamento.
  2. Dovresti visualizzare il riquadro inferiore di Fatturazione Google Play con i dettagli calcolati della sostituzione dell'abbonamento (ad esempio addebiti immediati e aggiustamenti del ciclo di fatturazione).

Carrello di fatturazione per la sostituzione dell&#39;abbonamento

  1. Completa la transazione.
  2. Vai alla pagina Gestisci abbonamenti nell'app Play Store per visualizzare le modifiche all'abbonamento attivo insieme ai dettagli sulle date di rinnovo e sui prezzi aggiornati.

Gestione degli abbonamenti del Play Store

13. Passaggi successivi

Documenti di riferimento

14. Complimenti

Complimenti! Hai implementato correttamente le sostituzioni degli abbonamenti con varie modalità di ripartizione proporzionale e configurato la gestione del backend per le transizioni dei piani.

Cosa hai imparato

  • Come configurare SubscriptionProductReplacementParams con modalità di sostituzione specifiche.
  • La differenza tra upgrade immediati e downgrade differiti.
  • Come ritirare i vecchi token di abbonamento utilizzando linkedPurchaseToken tramite RTDN.

Sondaggio

Il tuo feedback su questo codelab è molto importante. Ti invitiamo a dedicare qualche minuto per completare il nostro sondaggio.