> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscription Upgrade & Downgrade Guide

> Learn how to change a customer's subscription plan, handle proration, and process webhooks reliably.

<CardGroup cols={3}>
  <Card title="Change Plan API" icon="code" href="/api-reference/subscriptions/change-plan">
    Full API docs for updating subscriptions.
  </Card>

  <Card title="Plan Change Preview" icon="eye" href="/api-reference/subscriptions/preview-change-plan">
    See charge amounts before changing plans.
  </Card>

  <Card title="Integration Guide" icon="book" href="/developer-resources/subscription-integration-guide">
    Step-by-step subscription setup.
  </Card>
</CardGroup>

## What is a subscription upgrade or downgrade?

Changing plans lets you move a customer between subscription tiers or quantities. Use it to:

* Align pricing with usage or features
* Move from monthly to annual (or vice versa)
* Adjust quantity for seat-based products

<Info>
  Plan changes can trigger an immediate charge depending on the proration mode you choose.
</Info>

## When to use plan changes

* Upgrade when a customer needs more features, usage, or seats
* Downgrade when usage decreases
* Migrate users to a new product or price without cancelling their subscription

## Plan Change Flow

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant A as Your App
    participant D as Dodo Payments
    C->>A: Request plan change
    A->>D: Preview change
    D-->>A: Charge breakdown
    A->>C: Show cost details
    C->>A: Confirm
    A->>D: Change plan API call
    D->>D: Prorate & charge
    D-->>A: Result + invoice
    D->>A: Webhooks (payment, plan_changed)
    A->>C: Plan updated
```

## Prerequisites

Before implementing subscription plan changes, ensure you have:

* A Dodo Payments merchant account with active subscription products
* API credentials (API key and webhook secret key) from the dashboard
* An existing active subscription to modify
* Webhook endpoint configured to handle subscription events

<Info>
  For detailed setup instructions, see our [Integration Guide](/developer-resources/integration-guide#dashboard-setup).
</Info>

## Step-by-Step Implementation Guide

Follow this comprehensive guide to implement subscription plan changes in your application:

<Steps>
  <Step title="Understand Plan Change Requirements">
    Before implementing, determine:

    * Which subscription products can be changed to which others
    * What proration mode fits your business model
    * How to handle failed plan changes gracefully
    * Which webhook events to track for state management

    <Tip>
      Test plan changes thoroughly in test mode before implementing in production.
    </Tip>
  </Step>

  <Step title="Choose Your Proration Strategy">
    Select the billing approach that aligns with your business needs:

    <Tabs>
      <Tab title="prorated_immediately">
        Best for: SaaS applications wanting to charge fairly for unused time

        * Calculates exact prorated amount based on remaining cycle time
        * Charges a prorated amount based on unused time remaining in the cycle
        * Provides transparent billing to customers
      </Tab>

      <Tab title="difference_immediately">
        Best for: Clear upgrade/downgrade scenarios

        * Upgrade: Charge immediate difference (e.g., $30→$80 = charge \$50)
        * Downgrade: Credit remaining value for future renewals
        * Simplifies billing logic and customer communication
      </Tab>

      <Tab title="full_immediately">
        Best for: When you want to reset the billing cycle

        * Charges full amount of new plan immediately
        * Ignores remaining time from old plan
        * Useful for annual to monthly transitions
      </Tab>

      <Tab title="do_not_bill">
        Best for: Free migrations, courtesy switches, or absorbing cost differences

        * No charges or credits calculated
        * Customer moves to the new plan immediately without any billing adjustment
        * Billing cycle remains unchanged
      </Tab>
    </Tabs>
  </Step>

  <Step title="Implement the Change Plan API">
    Use the Change Plan API to modify subscription details:

    <ParamField path="subscription_id" type="string" required>
      The ID of the active subscription to modify.
    </ParamField>

    <ParamField path="product_id" type="string" required>
      The new product ID to change the subscription to.
    </ParamField>

    <ParamField path="quantity" type="integer" required>
      Nombre d'unités pour le nouveau plan (pour les produits basés sur le nombre de sièges).
    </ParamField>

    <ParamField path="proration_billing_mode" type="string" required>
      How to handle immediate billing: `prorated_immediately`, `full_immediately`, `difference_immediately`, or `do_not_bill`.
    </ParamField>

    <ParamField path="addons" type="array">
      Optional addons for the new plan. Leaving this empty removes any existing addons.
    </ParamField>

    <ParamField path="on_payment_failure" type="string">
      Controls behavior when the plan change payment fails:

      * `prevent_change`: Keep subscription on current plan until payment succeeds
      * `apply_change` (default): Apply plan change immediately regardless of payment outcome

      If not specified, uses the business-level default setting.
    </ParamField>

    <ParamField path="discount_codes" type="array">
      Codes promo **empilables** optionnels à appliquer au nouveau plan (max 20, appliqués dans l'ordre du tableau). Le comportement dépend de ce que vous transmettez :

      * **Non fourni / `null`** — les remises existantes avec `preserve_on_plan_change=true` sont conservées si elles s'appliquent au nouveau produit.
      * **`[]` (tableau vide)** — supprime toutes les remises existantes de l'abonnement.
      * **`["CODE_A", "CODE_B", ...]`** — remplace toutes les remises existantes par cet ensemble empilable.
    </ParamField>

    <ParamField path="discount_code" type="string" deprecated>
      **Obsolète** — privilégier `discount_codes` pour les nouvelles intégrations. Ce champ fonctionne toujours pour assurer la compatibilité ascendante, mais ne peut pas être combiné avec `discount_codes` dans la même requête.
    </ParamField>

    <ParamField path="effective_at" type="string" default="immediately">
      Quand appliquer le changement de plan :

      * `immediately` (par défaut) : Appliquer immédiatement le changement de plan
      * `next_billing_date` : Programmer le changement pour la prochaine date de facturation. Le client conserve son plan actuel jusqu'à la fin de la période de facturation.

      Utilisez `next_billing_date` pour les rétrogradations, afin que les clients gardent les avantages de leur plan actuel jusqu'à la fin de la période de facturation.
    </ParamField>
  </Step>

  <Step title="Handle Webhook Events">
    Configurez la gestion des webhooks pour suivre les résultats des changements de plan :

    * `subscription.active` : Changement de plan réussi, abonnement mis à jour
    * `subscription.plan_changed` : Plan d'abonnement changé (mise à niveau/rétrogradation/mise à jour des modules complémentaires)
    * `subscription.on_hold` : Échec du prélèvement pour le changement de plan, abonnement mis en pause
    * `payment.succeeded` : Prélèvement immédiat pour changement de plan réussi
    * `payment.failed` : Échec du prélèvement immédiat

    <Warning>
      Vérifiez toujours les signatures des webhooks et implémentez un traitement idempotent des événements.
    </Warning>
  </Step>

  <Step title="Update Your Application State">
    Sur la base des événements des webhooks, mettez à jour votre application :

    * Accorder/révoquer des fonctionnalités en fonction du nouveau plan
    * Mettre à jour le tableau de bord client avec les nouveaux détails du plan
    * Envoyer des emails de confirmation concernant les changements de plan
    * Enregistrer les changements de facturation à des fins de vérification
  </Step>

  <Step title="Test and Monitor">
    Testez rigoureusement votre implémentation :

    * Testez tous les modes de proratisation avec différents scénarios
    * Vérifiez que la gestion des webhooks fonctionne correctement
    * Surveillez les taux de succès des changements de plan
    * Configurez des alertes pour les échecs de changement de plan

    <Check>
      Votre implémentation de changement de plan d'abonnement est maintenant prête pour une utilisation en production.
    </Check>
  </Step>
</Steps>

## Prévisualisation des changements de plan

Avant de valider un changement de plan, utilisez l'API de Prévisualisation pour montrer aux clients exactement ce qu'ils seront facturés :

<Tabs>
  <Tab title="Node.js SDK">
    ```javascript theme={null}
    const preview = await client.subscriptions.previewChangePlan('sub_123', {
      product_id: 'prod_pro',
      quantity: 1,
      proration_billing_mode: 'prorated_immediately'
    });

    // Show customer the charge before confirming
    console.log('Immediate charge:', preview.immediate_charge.summary);
    console.log('New plan details:', preview.new_plan);
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python theme={null}
    preview = client.subscriptions.preview_change_plan(
        subscription_id="sub_123",
        product_id="prod_pro",
        quantity=1,
        proration_billing_mode="prorated_immediately"
    )

    # Show customer the charge before confirming
    print("Immediate charge:", preview.immediate_charge.summary)
    print("New plan details:", preview.new_plan)
    ```
  </Tab>
</Tabs>

<Tip>
  Utilisez l'API de prévisualisation pour construire des boîtes de dialogue de confirmation montrant aux clients le montant exact qu'ils seront facturés avant de confirmer un changement de plan.
</Tip>

## API de Changement de Plan

Utilisez l'API de Changement de Plan pour modifier le produit, la quantité et le comportement de proratisation pour un abonnement actif.

### Exemples de démarrage rapide

<Tabs>
  <Tab title="Node.js SDK">
    ```javascript theme={null}
    import DodoPayments from 'dodopayments';

    const client = new DodoPayments({
      bearerToken: process.env.DODO_PAYMENTS_API_KEY,
      environment: 'test_mode', // defaults to 'live_mode'
    });

    async function changePlan() {
      const result = await client.subscriptions.changePlan('sub_123', {
        product_id: 'prod_new',
        quantity: 3,
        proration_billing_mode: 'prorated_immediately',
        on_payment_failure: 'prevent_change', // Optional: control behavior on payment failure
      });
      console.log(result.status, result.invoice_id, result.payment_id);
    }

    changePlan();
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python theme={null}
    import os
    from dodopayments import DodoPayments

    client = DodoPayments(
        bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"),
        environment="test_mode",  # defaults to "live_mode"
    )

    result = client.subscriptions.change_plan(
        subscription_id="sub_123",
        product_id="prod_new",
        quantity=3,
        proration_billing_mode="prorated_immediately",
        on_payment_failure="prevent_change",  # Optional: control behavior on payment failure
    )
    print(result.status, result.get("invoice_id"), result.get("payment_id"))
    ```
  </Tab>

  <Tab title="Go SDK">
    ```go theme={null}
    package main

    import (
      "context"
      "fmt"
      "github.com/dodopayments/dodopayments-go"
      "github.com/dodopayments/dodopayments-go/option"
    )

    func main() {
      client := dodopayments.NewClient(option.WithBearerToken("YOUR_TOKEN"))
      // ChangePlan returns no response body; a nil error means the change succeeded.
      err := client.Subscriptions.ChangePlan(context.TODO(), "sub_123", dodopayments.SubscriptionChangePlanParams{
        UpdateSubscriptionPlanReq: dodopayments.UpdateSubscriptionPlanReqParam{
          ProductID:            dodopayments.F("prod_new"),
          Quantity:             dodopayments.F(int64(3)),
          ProrationBillingMode: dodopayments.F(dodopayments.UpdateSubscriptionPlanReqProrationBillingModeProratedImmediately),
          OnPaymentFailure:     dodopayments.F(dodopayments.UpdateSubscriptionPlanReqOnPaymentFailurePreventChange), // Optional
        },
      })
      if err != nil { panic(err) }
      fmt.Println("Plan change applied")
    }
    ```
  </Tab>

  <Tab title="HTTP">
    ```bash theme={null}
    curl -X POST "$DODO_API_BASE/subscriptions/sub_123/change-plan" \
      -H "Authorization: Bearer $DODO_PAYMENTS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "product_id": "prod_new",
        "quantity": 3,
        "proration_billing_mode": "prorated_immediately",
        "on_payment_failure": "prevent_change"
      }'
    ```
  </Tab>
</Tabs>

```json Success theme={null}
{
  "status": "processing",
  "subscription_id": "sub_123",
  "invoice_id": "inv_789",
  "payment_id": "pay_456",
  "proration_billing_mode": "prorated_immediately"
}
```

<Note>
  Les champs comme <code>invoice\_id</code> et <code>payment\_id</code> sont retournés uniquement lorsqu'un prélèvement immédiat et/ou une facture sont créés lors du changement de plan. Toujours se fier aux événements des webhooks (e.g., <code>payment.succeeded</code>, <code>subscription.plan\_changed</code>) pour confirmer les résultats.
</Note>

<Warning>
  Si le prélèvement immédiat échoue, l'abonnement peut être déplacé vers `subscription.on_hold` jusqu'à ce que le paiement réussisse.
</Warning>

## Gestion des Modules Complémentaires

Lors du changement de plans d'abonnement, vous pouvez également modifier les modules complémentaires :

```javascript theme={null}
// Add addons to the new plan
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_new',
  quantity: 1,
  proration_billing_mode: 'difference_immediately',
  addons: [
    { addon_id: 'addon_123', quantity: 2 }
  ]
});

// Remove all existing addons
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_new',
  quantity: 1,
  proration_billing_mode: 'difference_immediately',
  addons: [] // Empty array removes all existing addons
});
```

<Info>
  Les modules complémentaires sont inclus dans le calcul de proratisation et seront facturés selon le mode de proratisation sélectionné.
</Info>

## Application de Codes Promo

Vous pouvez appliquer un ou plusieurs **codes promo empilables** lors du changement de plans d'abonnement (max 20, appliqués dans l'ordre du tableau). Ceci est utile pour offrir un tarif promotionnel sur des mises à niveau ou des migrations.

<Tabs>
  <Tab title="Node.js SDK">
    ```javascript theme={null}
    // Apply stacked discount codes during plan change
    await client.subscriptions.changePlan('sub_123', {
      product_id: 'prod_pro',
      quantity: 1,
      proration_billing_mode: 'prorated_immediately',
      discount_codes: ['UPGRADE20']
    });
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python theme={null}
    # Apply stacked discount codes during plan change
    client.subscriptions.change_plan(
        subscription_id="sub_123",
        product_id="prod_pro",
        quantity=1,
        proration_billing_mode="prorated_immediately",
        discount_codes=["UPGRADE20"]
    )
    ```
  </Tab>

  <Tab title="HTTP">
    ```bash theme={null}
    curl -X POST "$DODO_API_BASE/subscriptions/sub_123/change-plan" \
      -H "Authorization: Bearer $DODO_PAYMENTS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "product_id": "prod_pro",
        "quantity": 1,
        "proration_billing_mode": "prorated_immediately",
        "discount_codes": ["UPGRADE20"]
      }'
    ```
  </Tab>
</Tabs>

### Comportement des remises lors du changement de plan

| `discount_codes` valeur     | Comportement                                                                                                                         |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Non fourni / `null`         | Les remises existantes avec `preserve_on_plan_change=true` sont automatiquement conservées si elles s'appliquent au nouveau produit. |
| `[]` (tableau vide)         | **Toutes** les remises existantes sont supprimées de l'abonnement.                                                                   |
| `["CODE_A", "CODE_B", ...]` | Remplace toutes les remises existantes par cet ensemble empilable, validé et appliqué dans l'ordre du tableau.                       |

<Info>
  Le champ singulier `discount_code` sur ce point de terminaison est **obsolète** mais fonctionne toujours pour assurer la compatibilité ascendante — les intégrations existantes n'ont pas besoin de changer immédiatement. Il ne peut pas être combiné avec `discount_codes` dans la même requête. Passez à la forme tableau quand cela est pratique.
</Info>

<Tip>
  Utilisez l'[API de Prévisualisation de Changement de Plan](/api-reference/subscriptions/preview-change-plan) avec `discount_codes` pour montrer exactement aux clients combien ils économiseront avant de confirmer le changement de plan.
</Tip>

## Modes de Proratisation

Choisissez comment facturer le client lors des changements de plan :

#### `prorated_immediately`

* Facture pour la différence partielle dans le cycle actuel
* Si en période d'essai, facture immédiatement et passe maintenant au nouveau plan
* Rétrogradation : peut générer un crédit au prorata appliqué aux renouvellements futurs

#### `full_immediately`

* Facture le montant total du nouveau plan immédiatement
* Ignore le temps restant du plan ancien

<Info>
  Les crédits créés par les rétrogradations en utilisant <code>difference\_immediately</code> sont liés à l'abonnement et distincts des droits de <a href="/features/credit-based-billing">Facturation Basée sur le Crédit</a>. Ils s'appliquent automatiquement aux renouvellements futurs du même abonnement et ne sont pas transférables d'un abonnement à un autre.
</Info>

#### `difference_immediately`

* Mise à niveau : facture immédiatement la différence de prix entre les anciens et les nouveaux plans
* Rétrogradation : ajoute la valeur restante en crédit interne à l'abonnement et l'applique automatiquement aux renouvellements

#### `do_not_bill`

* Aucun frais ou crédit n'est calculé
* Le client passe immédiatement au nouveau plan sans ajustement de facturation
* Le cycle de facturation reste inchangé
* Idéal pour les migrations de courtoisie, les changements de plan gratuits ou l'absorption des différences de coût

| Fonctionnalité                   | `prorated_immediately`                        | `difference_immediately`                      | `full_immediately`                          | `do_not_bill`                                     |
| -------------------------------- | --------------------------------------------- | --------------------------------------------- | ------------------------------------------- | ------------------------------------------------- |
| **Frais de mise à niveau**       | Différence proratisée pour les jours restants | Différence de prix totale entre les plans     | Prix total du nouveau plan                  | Aucun frais                                       |
| **Crédit de rétrogradation**     | Crédit proratisé pour les jours restants      | Différence de prix totale comme crédit        | Aucun crédit                                | Aucun crédit                                      |
| **Cycle de facturation**         | Inchangé                                      | Inchangé                                      | Réinitialisé à aujourd'hui                  | Inchangé                                          |
| **Comportement pendant l'essai** | Met fin à l'essai, facture immédiatement      | Met fin à l'essai, facture immédiatement      | Met fin à l'essai, facture le montant total | Met fin à l'essai, aucun frais                    |
| **Idéal pour**                   | Facturation équitable basée sur le temps      | Calcul simple de mise à niveau/rétrogradation | Réinitialisation des cycles de facturation  | Migrations gratuites ou changements de courtoisie |
| **Complexité**                   | Moyenne (calcul de jour)                      | Faible (soustraction simple)                  | Faible (montant total)                      | Aucune                                            |

```mermaid theme={null}
flowchart TD
    A{Need fair day-by-day billing?} -->|Yes| B[prorated_immediately]
    A -->|No| C{Need to reset billing cycle?}
    C -->|Yes| D[full_immediately]
    C -->|No| E{Skip billing entirely?}
    E -->|Yes| F[do_not_bill]
    E -->|No| G[difference_immediately]
```

### Scénarios d'exemple

Utilisez ces chiffres canoniques de manière cohérente :

* Plan actuel : **Basique** à **30 \$/mois**
* Objectif de mise à niveau : **Pro** à **80 \$/mois**
* Objectif de rétrogradation (à partir de Pro) : **Starter** à **20 \$/mois**
* Cycle de facturation : **30 jours**, commencé le **1er janvier**
* Changement de plan prévu pour le **16 janvier** (15 jours restants, 15 jours utilisés)

<AccordionGroup>
  <Accordion title="Upgrade: Basic ($30) → Pro ($80) with prorated_immediately">
    ```
    Step 1: Calculate unused credit from current plan
      Unused days = 15 out of 30 days
      Credit = $30 × (15/30) = $15.00

    Step 2: Calculate prorated cost of new plan
      Remaining days = 15 out of 30 days
      New plan cost = $80 × (15/30) = $40.00

    Step 3: Calculate immediate charge
      Charge = New plan cost − Credit
      Charge = $40.00 − $15.00 = $25.00

    → Customer pays $25.00 now
    → Next renewal (Feb 1): $80.00/month
    ```

    ```javascript theme={null}
    await client.subscriptions.changePlan('sub_123', {
      product_id: 'prod_pro',
      quantity: 1,
      proration_billing_mode: 'prorated_immediately'
    })
    ```
  </Accordion>

  <Accordion title="Downgrade: Pro ($80) → Starter ($20) with prorated_immediately">
    ```
    Step 1: Calculate unused credit from current plan
      Unused days = 15 out of 30 days
      Credit = $80 × (15/30) = $40.00

    Step 2: Calculate prorated cost of new plan
      Remaining days = 15 out of 30 days
      New plan cost = $20 × (15/30) = $10.00

    Step 3: Calculate credit balance
      Credit = $40.00 − $10.00 = $30.00

    → No charge — $30.00 credit added to subscription
    → Credit auto-applies to future renewals
    → Next renewal (Feb 1): $20.00 − $30.00 credit = $0.00
    → Following renewal (Mar 1): $20.00 − $10.00 remaining credit = $10.00
    ```

    ```javascript theme={null}
    await client.subscriptions.changePlan('sub_123', {
      product_id: 'prod_starter',
      quantity: 1,
      proration_billing_mode: 'prorated_immediately'
    })
    ```
  </Accordion>

  <Accordion title="Upgrade: Basic ($30) → Pro ($80) with difference_immediately">
    ```
    Immediate charge = New plan price − Old plan price
                     = $80 − $30
                     = $50.00

    → Customer pays $50.00 now (regardless of cycle position)
    → Next renewal (Feb 1): $80.00/month
    ```

    ```javascript theme={null}
    await client.subscriptions.changePlan('sub_123', {
      product_id: 'prod_pro',
      quantity: 1,
      proration_billing_mode: 'difference_immediately'
    })
    ```
  </Accordion>

  <Accordion title="Downgrade: Pro ($80) → Starter ($20) with difference_immediately">
    ```
    Credit = Old plan price − New plan price
           = $80 − $20
           = $60.00

    → No charge — $60.00 credit added to subscription
    → Credit auto-applies to future renewals
    → Next renewal: $20.00 − $20.00 (from credit) = $0.00
    → Following renewal: $20.00 − $20.00 (from credit) = $0.00
    → Third renewal: $20.00 − $20.00 (from remaining credit) = $0.00
    ```

    ```javascript theme={null}
    await client.subscriptions.changePlan('sub_123', {
      product_id: 'prod_starter',
      quantity: 1,
      proration_billing_mode: 'difference_immediately'
    })
    ```
  </Accordion>

  <Accordion title="Upgrade: Basic ($30) → Pro ($80) with full_immediately">
    ```
    Immediate charge = Full new plan price = $80.00

    → Customer pays $80.00 now
    → No credit for unused time on old plan
    → Billing cycle resets to today (January 16)
    → Next renewal: February 16 at $80.00/month
    ```

    ```javascript theme={null}
    await client.subscriptions.changePlan('sub_123', {
      product_id: 'prod_pro',
      quantity: 1,
      proration_billing_mode: 'full_immediately'
    })
    ```
  </Accordion>

  <Accordion title="Mid-cycle upgrade with add-ons using prorated_immediately">
    ```
    Current: Basic plan ($30/month), no add-ons
    New: Pro plan ($80/month) + Extra Seats add-on ($10/seat × 3 seats = $30/month)
    Change on day 16 of 30 (15 days remaining)

    Step 1: Credit from current plan
      Credit = $30 × (15/30) = $15.00

    Step 2: Prorated cost of new plan + add-ons
      New plan = $80 × (15/30) = $40.00
      Add-ons = $30 × (15/30) = $15.00
      Total new = $55.00

    Step 3: Immediate charge
      Charge = $55.00 − $15.00 = $40.00

    → Customer pays $40.00 now
    → Next renewal: $80.00 + $30.00 = $110.00/month
    ```

    ```javascript theme={null}
    await client.subscriptions.changePlan('sub_123', {
      product_id: 'prod_pro',
      quantity: 1,
      proration_billing_mode: 'prorated_immediately',
      addons: [
        { addon_id: 'addon_seats', quantity: 3 }
      ]
    })
    ```
  </Accordion>
</AccordionGroup>

### Comment chaque mode traite la facturation

```mermaid theme={null}
flowchart LR
    A[Plan Change] --> B{Proration Mode}
    B -->|prorated| C[Calculate remaining days]
    C --> D[Charge prorated diff]
    D --> E[Keep billing date]
    B -->|difference| F[Price difference]
    F -->|Upgrade| G[Charge diff]
    F -->|Downgrade| H[Add credit]
    G --> E
    H --> E
    B -->|full| I[Charge full price]
    I --> J[Reset billing date]
```

<Tip>
  Choisissez `prorated_immediately` pour une comptabilité équitable dans le temps ; choisissez `full_immediately` pour redémarrer la facturation ; utilisez `difference_immediately` pour des mises à niveau simples et des crédits automatiques lors de rétrogradations ; ou utilisez `do_not_bill` pour changer de plan sans ajustement de facturation.
</Tip>

## Gestion des Échecs de Paiement

Contrôlez ce qui se passe lorsqu'un paiement de changement de plan échoue en utilisant le paramètre `on_payment_failure`.

### Modes d'Échec de Paiement

<Tabs>
  <Tab title="prevent_change (Recommended for critical upgrades)">
    **Comportement** : Conservez l'abonnement sur son plan actuel jusqu'à ce que le paiement réussisse.

    * Le changement de plan est marqué "en attente"
    * Le client conserve l'accès à son plan actuel
    * L'abonnement se déplace vers l'état `active` seulement après un paiement réussi
    * Utile lorsque vous souhaitez garantir le paiement avant d'accorder des fonctionnalités améliorées

    ```javascript theme={null}
    await client.subscriptions.changePlan('sub_123', {
      product_id: 'prod_pro',
      quantity: 1,
      proration_billing_mode: 'prorated_immediately',
      on_payment_failure: 'prevent_change'
    });
    ```
  </Tab>

  <Tab title="apply_change (Default)">
    **Comportement** : Appliquer le changement de plan immédiatement, quel que soit le résultat du paiement.

    * Le changement de plan est appliqué même si le paiement échoue
    * Le client obtient un accès immédiat au nouveau plan
    * L'abonnement peut se déplacer vers `on_hold` si le paiement échoue
    * Bon pour des mises à niveau non critiques ou lorsque vous faites confiance au client

    ```javascript theme={null}
    await client.subscriptions.changePlan('sub_123', {
      product_id: 'prod_pro',
      quantity: 1,
      proration_billing_mode: 'prorated_immediately',
      on_payment_failure: 'apply_change' // This is the default
    });
    ```
  </Tab>
</Tabs>

<Info>
  Si non spécifié, le paramètre `on_payment_failure` utilise votre paramètre par défaut au niveau de l'entreprise configuré dans le tableau de bord.
</Info>

### Quand Utiliser Chaque Mode

| Scénario                                       | Mode Recommandé  | Raison                                       |
| ---------------------------------------------- | ---------------- | -------------------------------------------- |
| Mise à niveau vers des fonctionnalités premium | `prevent_change` | Assurez le paiement avant d'accorder l'accès |
| Augmentation de la quantité (plus de sièges)   | `prevent_change` | Prévenir l'usage sans paiement               |
| Rétrogradation des plans                       | `apply_change`   | Le client réduit ses dépenses                |
| Clients d'entreprise de confiance              | `apply_change`   | Risque de non-paiement faible                |
| Conversion de l'essai en abonnement payant     | `prevent_change` | Moment de paiement critique                  |

## Gestion des Webhooks

Suivez l'état des abonnements via les webhooks pour confirmer les changements de plan et les paiements.

### Types d'Événements à Gérer

* `subscription.active` : abonnement activé
* `subscription.plan_changed` : Plan d'abonnement modifié (mise à niveau/rétrogradation/changements de modules complémentaires)
* `subscription.on_hold` : Échec du prélèvement, abonnement mis en pause
* `subscription.renewed` : renouvellement réussi
* `payment.succeeded` : Prélèvement pour changement de plan ou renouvellement réussi
* `payment.failed` : Échec du paiement

<Info>
  Nous recommandons de piloter la logique commerciale à partir des événements d'abonnement et d'utiliser les événements de paiement pour confirmation et réconciliation.
</Info>

### Vérification des Signatures et Gestion des Intentions

<Tabs>
  <Tab title="Next.js Route Handler">
    ```javascript theme={null}
    import { NextRequest, NextResponse } from 'next/server';

    export async function POST(req) {
      const webhookId = req.headers.get('webhook-id');
      const webhookSignature = req.headers.get('webhook-signature');
      const webhookTimestamp = req.headers.get('webhook-timestamp');
      const secret = process.env.DODO_WEBHOOK_SECRET;

      const payload = await req.text();
      // verifySignature is a placeholder – in production, use a Standard Webhooks library
      const { valid, event } = await verifySignature(
        payload,
        { id: webhookId, signature: webhookSignature, timestamp: webhookTimestamp },
        secret
      );
      if (!valid) return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });

      switch (event.type) {
        case 'subscription.active':
          // mark subscription active in your DB
          break;
        case 'subscription.plan_changed':
          // refresh entitlements and reflect the new plan in your UI
          break;
        case 'subscription.on_hold':
          // notify user to update payment method
          break;
        case 'subscription.renewed':
          // extend access window
          break;
        case 'payment.succeeded':
          // reconcile payment for plan change
          break;
        case 'payment.failed':
          // log and alert
          break;
        default:
          // ignore unknown events
          break;
      }

      return NextResponse.json({ received: true });
    }
    ```
  </Tab>

  <Tab title="Express.js">
    ```javascript theme={null}
    import express from 'express';

    const app = express();
    app.post('/webhooks/dodo', express.raw({ type: 'application/json' }), async (req, res) => {
      const webhookId = req.header('webhook-id');
      const webhookSignature = req.header('webhook-signature');
      const webhookTimestamp = req.header('webhook-timestamp');
      const secret = process.env.DODO_WEBHOOK_SECRET;
      const payload = req.body.toString('utf8');

      const { valid, event } = await verifySignature(
        payload,
        { id: webhookId, signature: webhookSignature, timestamp: webhookTimestamp },
        secret
      );
      if (!valid) return res.status(400).send('Invalid signature');

      // handle events like above
      res.json({ received: true });
    });

    app.listen(3000);
    ```
  </Tab>
</Tabs>

<Note>
  Pour des schémas de charge utile détaillés, consultez les <a href="/developer-resources/webhooks/intents/subscription">charges utiles des webhooks d'abonnement</a> et les <a href="/developer-resources/webhooks/intents/payment">charges utiles des webhooks de paiement</a>.
</Note>

## Meilleures Pratiques

Suivez ces recommandations pour des changements de plan d'abonnement fiables :

### Stratégie de Changement de Plan

* **Tester rigoureusement** : Testez toujours les changements de plan en mode test avant la production
* **Choisir la proratisation avec soin** : Sélectionnez le mode de proratisation qui s'aligne avec votre modèle commercial
* **Gérer les échecs avec soin** : Mettez en œuvre une gestion appropriée des erreurs et une logique de reprise
* **Surveiller les taux de succès** : Suivez les taux de succès/échec des changements de plan et examinez les problèmes

### Implémentation des Webhooks

* **Vérifiez les signatures** : Validez toujours les signatures des webhooks pour garantir leur authenticité
* **Implémentez l'idempotence** : Gérez avec soin les événements de webhook en double
* **Traitez de manière asynchrone** : Ne bloquez pas les réponses des webhooks avec des opérations lourdes
* **Tout enregistrer** : Maintenez des journaux détaillés pour le débogage et la vérification

### Expérience Utilisateur

* **Communiquez clairement** : Informez les clients des changements de facturation et de leur calendrier
* **Fournissez des confirmations** : Envoyez des confirmations par email pour les changements de plan réussis
* **Gérez les cas particuliers** : Considérez les périodes d'essai, les proratisations et les paiements échoués
* **Mettez à jour l'interface utilisateur immédiatement** : Réflétez les changements de plan dans l'interface de votre application

## Problèmes Courants et Solutions

Résolvez les problèmes typiques rencontrés lors des changements de plan d'abonnement :

<AccordionGroup>
  <Accordion title="Charge created but subscription not updated">
    **Symptômes** : L'appel API réussit, mais l'abonnement reste sur l'ancien plan

    **Causes courantes** :

    * Le traitement des webhooks a échoué ou a été retardé
    * L'état de l'application n'a pas été mis à jour après la réception des webhooks
    * Problèmes de transaction de base de données lors de la mise à jour de l'état

    **Solutions** :

    * Implémentez une gestion robuste des webhooks avec une logique de reprise
    * Utilisez des opérations idempotentes pour les mises à jour d'état
    * Ajoutez une surveillance pour détecter et alerter sur les événements de webhook manqués
    * Vérifiez que le point de terminaison du webhook est accessible et répond correctement
  </Accordion>

  <Accordion title="Credits not applied after downgrade">
    **Symptômes** : Le client rétrograde, mais ne voit pas le solde de crédit

    **Causes courantes** :

    * Attentes de mode de proratisation : les rétrogradations créditent la différence totale de prix du plan avec `difference_immediately`, tandis que `prorated_immediately` crée un crédit proratisé basé sur le temps restant dans le cycle
    * Les crédits sont spécifiques à un abonnement et ne se transfèrent pas entre abonnements
    * Solde de crédit non visible dans le tableau de bord client

    **Solutions** :

    * Utilisez `difference_immediately` pour les rétrogradations lorsque vous souhaitez des crédits automatiques
    * Expliquez aux clients que les crédits s'appliquent aux renouvellements futurs du même abonnement
    * Implémentez un portail client pour montrer les soldes de crédit
    * Vérifiez l'aperçu de la prochaine facture pour voir les crédits appliqués
  </Accordion>

  <Accordion title="Webhook signature verification fails">
    **Symptômes** : Événements de webhook rejetés en raison d'une signature invalide

    **Causes courantes** :

    * Clé secrète de webhook incorrecte
    * Corps de requête brute modifié avant la vérification de la signature
    * Mauvais algorithme de vérification de la signature

    **Solutions** :

    * Vérifiez que vous utilisez le bon `DODO_WEBHOOK_SECRET` depuis le tableau de bord
    * Lire le corps de la requête brute avant tout middleware de parsing JSON
    * Utilisez la bibliothèque standard de vérification de signature de webhook pour votre plateforme
    * Testez la vérification des signatures de webhook dans l'environnement de développement
  </Accordion>

  <Accordion title="Plan change fails with 422 error">
    **Symptômes** : L'API renvoie une erreur 422 Unprocessable Entity

    **Causes courantes** :

    * ID d'abonnement ou ID de produit invalide
    * Abonnement non en état actif
    * Paramètres requis manquants
    * Produit non disponible pour les changements de plan

    **Solutions** :

    * Vérifiez que l'abonnement existe et est actif
    * Assurez-vous que l'ID du produit est valide et disponible
    * Assurez-vous que tous les paramètres requis sont fournis
    * Consultez la documentation de l'API pour les exigences en matière de paramètres
  </Accordion>

  <Accordion title="Immediate charge fails during plan change">
    **Symptômes** : Le changement de plan est initié, mais le prélèvement immédiat échoue

    **Causes courantes** :

    * Fonds insuffisants sur le moyen de paiement du client
    * Moyen de paiement expiré ou invalide
    * La banque a refusé la transaction
    * La détection de fraude a bloqué le prélèvement

    **Solutions** :

    * Gérez correctement les événements de webhook `payment.failed`
    * Notifiez le client pour mettre à jour son moyen de paiement
    * Implémentez une logique de reprise pour les échecs temporaires
    * Envisagez de permettre des changements de plan avec des prélèvements immédiats échoués
  </Accordion>

  <Accordion title="Subscription on hold after plan change">
    **Symptômes** : Échec du prélèvement de changement de plan et l'abonnement passe en état `on_hold`

    **Que se passe-t-il** :
    Lorsque le prélèvement de changement de plan échoue, l'abonnement est automatiquement placé en état `on_hold`. L'abonnement ne se renouvellera pas automatiquement tant que le moyen de paiement n'est pas mis à jour.

    **Solution** : Mettez à jour le moyen de paiement pour réactiver l'abonnement

    Pour réactiver un abonnement depuis l'état `on_hold` après un échec de changement de plan :

    1. **Mettez à jour le moyen de paiement** en utilisant l'API de Mise à Jour de Moyen de Paiement
    2. **Création automatique de prélèvement** : L'API crée automatiquement un prélèvement pour les montants dus restants
    3. **Génération de facture** : Une facture est générée pour le prélèvement
    4. **Traitement du paiement** : Le paiement est traité à l'aide du nouveau moyen de paiement
    5. **Réactivation** : Après un paiement réussi, l'abonnement est réactivé à l'état `active`

    <CodeGroup>
      ```javascript Node.js theme={null}
      // Reactivate subscription from on_hold after failed plan change
      async function reactivateAfterFailedPlanChange(subscriptionId) {
        // Update payment method - automatically creates charge for remaining dues
        const response = await client.subscriptions.updatePaymentMethod(subscriptionId, {
          type: 'new',
          return_url: 'https://example.com/return'
        });
        
        if (response.payment_id) {
          console.log('Charge created for remaining dues:', response.payment_id);
          console.log('Payment link:', response.payment_link);
          
          // Redirect customer to payment_link to complete payment
          // Monitor webhooks for:
          // 1. payment.succeeded - charge succeeded
          // 2. subscription.active - subscription reactivated
        }
        
        return response;
      }

      // Or use existing payment method if available
      async function reactivateWithExistingPaymentMethod(subscriptionId, paymentMethodId) {
        const response = await client.subscriptions.updatePaymentMethod(subscriptionId, {
          type: 'existing',
          payment_method_id: paymentMethodId
        });
        
        // Monitor webhooks for payment.succeeded and subscription.active
        return response;
      }
      ```

      ```python Python theme={null}
      # Reactivate subscription from on_hold after failed plan change
      def reactivate_after_failed_plan_change(subscription_id):
          # Update payment method - automatically creates charge for remaining dues
          response = client.subscriptions.update_payment_method(
              subscription_id=subscription_id,
              type="new",
              return_url="https://example.com/return"
          )
          
          if response.payment_id:
              print("Charge created for remaining dues:", response.payment_id)
              print("Payment link:", response.payment_link)
              
              # Redirect customer to payment_link to complete payment
              # Monitor webhooks for:
              # 1. payment.succeeded - charge succeeded
              # 2. subscription.active - subscription reactivated
          
          return response

      # Or use existing payment method if available
      def reactivate_with_existing_payment_method(subscription_id, payment_method_id):
          response = client.subscriptions.update_payment_method(
              subscription_id=subscription_id,
              type="existing",
              payment_method_id=payment_method_id
          )
          
          # Monitor webhooks for payment.succeeded and subscription.active
          return response
      ```
    </CodeGroup>

    **Événements de webhook à surveiller** :

    * `subscription.on_hold`: Abonnement mis en attente (reçu lorsque le prélèvement de changement de plan échoue)
    * `payment.succeeded`: Paiement des montants dus réussi (après mise à jour du moyen de paiement)
    * `subscription.active`: Abonnement réactivé après paiement réussi

    **Meilleures pratiques** :

    * Notifiez immédiatement les clients lorsqu'un prélèvement de changement de plan échoue
    * Fournissez des instructions claires sur la façon de mettre à jour leur moyen de paiement
    * Surveillez les événements des webhooks pour suivre le statut de réactivation
    * Envisagez d'implémenter une logique de reprise automatique pour les échecs de paiement temporaires

    <Card title="Update Payment Method API Reference" icon="code" href="/api-reference/subscriptions/update-payment-method">
      Consultez la documentation API complète pour la mise à jour des moyens de paiement et la réactivation des abonnements.
    </Card>
  </Accordion>
</AccordionGroup>

## Test de votre implémentation

Suivez ces étapes pour tester rigoureusement l'implémentation de votre changement de plan d'abonnement :

<Steps>
  <Step title="Set up test environment">
    * Utilisez des clés API de test et des produits de test
    * Créez des abonnements de test avec différents types de plan
    * Configurez le point de terminaison de webhook de test
    * Configurez la surveillance et l'enregistrement
  </Step>

  <Step title="Test different proration modes">
    * Testez `prorated_immediately` avec diverses positions de cycle de facturation
    * Testez `difference_immediately` pour les mises à niveau et les rétrogradations
    * Testez `full_immediately` pour réinitialiser les cycles de facturation
    * Testez `do_not_bill` pour des changements de plan sans frais/ni crédit
    * Vérifiez que les calculs de crédit sont corrects
  </Step>

  <Step title="Test webhook handling">
    * Vérifiez que tous les événements de webhook pertinents sont reçus
    * Testez la vérification des signatures de webhook
    * Gérez avec soin les événements de webhook en double
    * Testez les scénarios d'échec de traitement de webhook
  </Step>

  <Step title="Test error scenarios">
    * Testez avec des ID d'abonnement invalides
    * Testez avec des moyens de paiement expirés
    * Testez les échecs de réseau et les délais d'attente
    * Testez avec des fonds insuffisants
  </Step>

  <Step title="Monitor in production">
    * Configurez des alertes pour les changements de plan échoués
    * Surveillez les temps de traitement des webhooks
    * Suivez les taux de réussite des changements de plan
    * Passez en revue les tickets de support client pour les problèmes de changement de plan
  </Step>
</Steps>

## Gestion des Erreurs

Gérez avec soin les erreurs communes de l'API dans votre implémentation :

### Codes d'État HTTP

<AccordionGroup>
  <Accordion title="200 OK">
    Requête de changement de plan traitée avec succès. L'abonnement est en cours de mise à jour et le traitement du paiement a commencé.
  </Accordion>

  <Accordion title="400 Bad Request">
    Paramètres de requête invalides. Vérifiez que tous les champs requis sont fournis et formatés correctement.
  </Accordion>

  <Accordion title="401 Unauthorized">
    Clé API invalide ou manquante. Vérifiez que votre `DODO_PAYMENTS_API_KEY` est correct et dispose des autorisations appropriées.
  </Accordion>

  <Accordion title="404 Not Found">
    ID d'abonnement introuvable ou n'appartient pas à votre compte.
  </Accordion>

  <Accordion title="422 Unprocessable Entity">
    L'abonnement ne peut pas être modifié (par exemple, déjà annulé, produit non disponible, etc.).
  </Accordion>

  <Accordion title="500 Internal Server Error">
    Une erreur de serveur s'est produite. Réessayez la requête après un bref délai.
  </Accordion>
</AccordionGroup>

### Format de Réponse d'Erreur

```json theme={null}
{
  "error": {
    "code": "subscription_not_found",
    "message": "The subscription with ID 'sub_123' was not found",
    "details": {
      "subscription_id": "sub_123"
    }
  }
}
```

## Prochaines étapes

* Consultez l'<a href="/api-reference/subscriptions/change-plan">API de Changement de Plan</a>
* Explorez la <a href="/features/credit-based-billing">Facturation Basée sur le Crédit</a>
* Implémentez des alertes pour `subscription.on_hold`
* Consultez notre <a href="/developer-resources/webhooks">Guide d'Intégration de Webhooks</a>

<AccordionGroup>
  <Accordion title="200 OK">
    Demande de changement de plan traitée avec succès. L'abonnement est en cours de mise à jour et le traitement du paiement a commencé.
  </Accordion>

  <Accordion title="400 Bad Request">
    Paramètres de requête invalides. Vérifiez que tous les champs obligatoires sont fournis et correctement formatés.
  </Accordion>

  <Accordion title="401 Unauthorized">
    Clé API invalide ou manquante. Vérifiez que votre `DODO_PAYMENTS_API_KEY` est correcte et dispose des autorisations appropriées.
  </Accordion>

  <Accordion title="404 Not Found">
    ID d'abonnement introuvable ou n'appartenant pas à votre compte.
  </Accordion>

  <Accordion title="422 Unprocessable Entity">
    L'abonnement ne peut pas être modifié (par exemple, déjà annulé, produit non disponible, etc.).
  </Accordion>

  <Accordion title="500 Internal Server Error">
    Erreur de serveur survenue. Réessayez la requête après un court délai.
  </Accordion>
</AccordionGroup>

### Format de réponse d'erreur

```json theme={null}
{
  "error": {
    "code": "subscription_not_found",
    "message": "The subscription with ID 'sub_123' was not found",
    "details": {
      "subscription_id": "sub_123"
    }
  }
}
```

## Prochaines étapes

* Consultez l'<a href="/api-reference/subscriptions/change-plan">API de changement de plan</a>
* Explorez la <a href="/features/credit-based-billing">Facturation Basée sur le Crédit</a>
* Mettez en œuvre des alertes pour `subscription.on_hold`
* Consultez notre <a href="/developer-resources/webhooks">Guide d'Intégration des Webhooks</a>
