> ## 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>
      Número de unidades para el nuevo plan (para productos basados en asientos).
    </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">
      Códigos de descuento **apilados** opcionales para aplicar al nuevo plan (máx. 20, aplicado en el orden del array). El comportamiento depende de lo que pases:

      * **No proporcionado / `null`** — los descuentos existentes con `preserve_on_plan_change=true` se conservan si son aplicables al nuevo producto.
      * **`[]` (array vacío)** — elimina todos los descuentos existentes de la suscripción.
      * **`["CODE_A", "CODE_B", ...]`** — reemplaza cualquier descuento existente con este conjunto apilado.
    </ParamField>

    <ParamField path="discount_code" type="string" deprecated>
      **Obsoleto** — se prefiere `discount_codes` para nuevas integraciones. Este campo sigue funcionando para compatibilidad con versiones anteriores, pero no puede combinarse con `discount_codes` en la misma solicitud.
    </ParamField>

    <ParamField path="effective_at" type="string" default="immediately">
      Cuándo aplicar el cambio de plan:

      * `immediately` (por defecto): Aplicar el cambio de plan de inmediato
      * `next_billing_date`: Programar el cambio para la próxima fecha de facturación. El cliente conserva su plan actual hasta que finalice el período de facturación.

      Usa `next_billing_date` para degradaciones para que los clientes mantengan los beneficios de su plan actual hasta el final del período de facturación.
    </ParamField>
  </Step>

  <Step title="Handle Webhook Events">
    Configurar el manejo de webhooks para rastrear los resultados del cambio de plan:

    * `subscription.active`: Cambio de plan exitoso, suscripción actualizada
    * `subscription.plan_changed`: Plan de suscripción cambiado (mejora/degradación/actualización de complemento)
    * `subscription.on_hold`: Fallo al cobrar el cambio de plan, suscripción pausada
    * `payment.succeeded`: Cargo inmediato por cambio de plan exitoso
    * `payment.failed`: Fallo en el cargo inmediato

    <Warning>
      Siempre verifica las firmas de los webhooks e implementa el procesamiento idempotente de eventos.
    </Warning>
  </Step>

  <Step title="Update Your Application State">
    Basado en eventos de webhooks, actualiza tu aplicación:

    * Otorgar/revocar funciones según el nuevo plan
    * Actualizar el panel de control del cliente con los detalles del nuevo plan
    * Enviar correos electrónicos de confirmación sobre los cambios de plan
    * Registrar cambios de facturación para fines de auditoría
  </Step>

  <Step title="Test and Monitor">
    Prueba a fondo tu implementación:

    * Prueba todos los modos de prorrateo con diferentes escenarios
    * Verifica que el manejo de webhooks funcione correctamente
    * Monitorea las tasas de éxito de cambios de plan
    * Configura alertas para cambios de plan fallidos

    <Check>
      Tu implementación de cambio de plan de suscripción está ahora lista para su uso en producción.
    </Check>
  </Step>
</Steps>

## Previsualizar Cambios de Plan

Antes de comprometerse con un cambio de plan, utiliza la API de Vista Previa para mostrar a los clientes exactamente lo que se les cobrará:

<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>
  Utiliza la API de vista previa para construir cuadros de diálogo de confirmación que muestren a los clientes la cantidad exacta que se les cobrará antes de que confirmen un cambio de plan.
</Tip>

## API de Cambio de Plan

Utiliza la API de Cambio de Plan para modificar el producto, la cantidad y el comportamiento de prorrateo para una suscripción activa.

### Ejemplos de inicio rápido

<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>
  Campos como <code>invoice\_id</code> y <code>payment\_id</code> se devuelven solo cuando se crea un cargo y/o factura inmediato durante el cambio de plan. Siempre confía en los eventos de webhooks (por ejemplo, <code>payment.succeeded</code>, <code>subscription.plan\_changed</code>) para confirmar resultados.
</Note>

<Warning>
  Si el cargo inmediato falla, la suscripción puede moverse a `subscription.on_hold` hasta que el pago tenga éxito.
</Warning>

## Gestión de Complementos

Al cambiar los planes de suscripción, también puedes modificar los complementos:

```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>
  Los complementos están incluidos en el cálculo de prorrateo y se cobrarán de acuerdo con el modo de prorrateo seleccionado.
</Info>

## Aplicar Códigos de Descuento

Puedes aplicar uno o más **códigos de descuento apilados** al cambiar los planes de suscripción (máx. 20, aplicado en el orden del array). Esto es útil para ofrecer precios promocionales en actualizaciones o migraciones.

<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>

### Comportamiento del descuento en el cambio de plan

| Valor de `discount_codes`   | Comportamiento                                                                                                                 |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| No proporcionado / `null`   | Los descuentos existentes con `preserve_on_plan_change=true` se conservan automáticamente si son aplicables al nuevo producto. |
| `[]` (array vacío)          | Se eliminan **todos** los descuentos existentes de la suscripción.                                                             |
| `["CODE_A", "CODE_B", ...]` | Reemplaza cualquier descuento existente con este conjunto apilado, validado y aplicado en el orden del array.                  |

<Info>
  El campo singular `discount_code` en este endpoint está **obsoleto** pero aún funciona para la compatibilidad con versiones anteriores: las integraciones existentes no necesitan cambiarse de inmediato. No puede combinarse con `discount_codes` en la misma solicitud. Migra a la forma de array cuando sea conveniente.
</Info>

<Tip>
  Usa la [API de Vista Previa de Cambios de Plan](/api-reference/subscriptions/preview-change-plan) con `discount_codes` para mostrar a los clientes exactamente cuánto ahorrarán antes de confirmar el cambio de plan.
</Tip>

## Modos de Prorrateo

Elige cómo facturar al cliente al cambiar de plan:

#### `prorated_immediately`

* Cargos por la diferencia parcial en el ciclo actual
* Si está en prueba, cobra inmediatamente y cambia al nuevo plan ahora
* Degradación: puede generar un crédito prorrateado aplicado a futuras renovaciones

#### `full_immediately`

* Cobra el monto completo del nuevo plan inmediatamente
* Ignora el tiempo restante del plan antiguo

<Info>
  Los créditos creados por degradaciones usando <code>difference\_immediately</code> están limitados a la suscripción y son distintos de los derechos de <a href="/features/credit-based-billing">Facturación Basada en Créditos</a>. Se aplican automáticamente a futuras renovaciones de la misma suscripción y no son transferibles entre suscripciones.
</Info>

#### `difference_immediately`

* Mejora: cobra de inmediato la diferencia de precio entre los planes viejo y nuevo
* Degradación: añade el valor restante como crédito interno a la suscripción y se aplica automáticamente en las renovaciones

#### `do_not_bill`

* No se calculan cargos ni créditos
* El cliente cambia al nuevo plan inmediatamente sin ningún ajuste de facturación
* El ciclo de facturación permanece sin cambios
* Ideal para migraciones de cortesía, cambios de plan gratuitos o absorción de diferencias de costo

| Feature              | `prorated_immediately`                                | `difference_immediately`                              | `full_immediately`                                       | `do_not_bill`                               |
| -------------------- | ----------------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------- |
| **Upgrade charge**   | Diferencia prorrateada por los días restantes         | Diferencia del precio completo entre planes           | Precio completo del nuevo plan                           | Sin cargo                                   |
| **Downgrade credit** | Crédito prorrateado por los días restantes            | Diferencia de precio completo como crédito            | Sin crédito                                              | Sin crédito                                 |
| **Billing cycle**    | Se restablece a hoy                                   | Se restablece a hoy                                   | Se restablece a hoy                                      | Sin cambios                                 |
| **Trial behavior**   | Termina el periodo de prueba, se cobra inmediatamente | Termina el periodo de prueba, se cobra inmediatamente | Termina el periodo de prueba, se cobra el monto completo | Termina el periodo de prueba, sin cargo     |
| **Best for**         | Facturación justa basada en el tiempo                 | Matemáticas simples de actualización/degradación      | Restablecer ciclos de facturación                        | Migraciones gratuitas o cambios de cortesía |
| **Complexity**       | Media (cálculo de días)                               | Baja (resta simple)                                   | Baja (cobro completo)                                    | Ninguna                                     |

```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]
```

### Escenarios de Ejemplo

Usa estos números canónicos de manera consistente:

* Plan actual: **Básico** a **\$30/mes**
* Objetivo de mejora: **Pro** a **\$80/mes**
* Objetivo de degradación (de Pro): **Starter** a **\$20/mes**
* Ciclo de facturación: **30 días**, comenzado el **1 de enero**
* El cambio de plan ocurre el **16 de enero** (15 días restantes, 15 días usados)

<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
    → Billing cycle resets to today (January 16)
    → Next renewal (Feb 15): $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
    → Billing cycle resets to today (January 16)
    → Credit auto-applies to future renewals
    → Next renewal (Feb 15): $20.00 − $20.00 (from credit) = $0.00 ($10.00 credit remaining)
    → Following renewal (Mar 15): $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)
    → Billing cycle resets to today (January 16)
    → Next renewal (Feb 15): $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>

### Cómo cada modo procesa la facturación

```mermaid theme={null}
flowchart LR
    A[Plan Change] --> B{Proration Mode}
    B -->|prorated| C[Calculate remaining days]
    C --> D[Charge prorated diff]
    D --> E[Reset 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 --> E
    B -->|do_not_bill| J[No charge / no credit]
    J --> K[Keep billing date]
```

<Tip>
  Elige `prorated_immediately` para contabilidad de tiempo justo; selecciona `full_immediately` para reiniciar la facturación; usa `difference_immediately` para mejoras simples y crédito automático en degradaciones; o usa `do_not_bill` para cambiar de plan sin ningún ajuste de facturación.
</Tip>

## Manejo de Fallos de Pago

Controla lo que sucede cuando falla el pago de un cambio de plan usando el parámetro `on_payment_failure`.

### Modos de Fallo de Pago

<Tabs>
  <Tab title="prevent_change (Recommended for critical upgrades)">
    **Comportamiento**: Mantén la suscripción en su plan actual hasta que el pago tenga éxito.

    * El cambio de plan se marca como "pendiente"
    * El cliente retiene el acceso a su plan actual
    * La suscripción se mueve al estado `active` solo después del pago exitoso
    * Útil cuando deseas asegurar el pago antes de otorgar funciones mejoradas

    ```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)">
    **Comportamiento**: Aplica el cambio de plan inmediatamente sin importar el resultado del pago.

    * El cambio de plan se aplica incluso si el pago falla
    * El cliente obtiene acceso inmediato al nuevo plan
    * La suscripción puede moverse a `on_hold` si el pago falla
    * Bueno para mejoras no críticas o cuando confías en el cliente

    ```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 no se especifica, el parámetro `on_payment_failure` utiliza la configuración predeterminada a nivel empresarial configurada en el panel de control.
</Info>

### Cuándo Usar Cada Modo

| Escenario                           | Modo Recomendado | Razón                                   |
| ----------------------------------- | ---------------- | --------------------------------------- |
| Mejora a funciones premium          | `prevent_change` | Asegura el pago antes de otorgar acceso |
| Aumento de cantidad (más asientos)  | `prevent_change` | Evita el uso sin pago                   |
| Degradación de planes               | `apply_change`   | El cliente está reduciendo el gasto     |
| Clientes empresariales de confianza | `apply_change`   | Menor riesgo de incumplimiento de pago  |
| Conversión de prueba a pago         | `prevent_change` | Momento crítico de pago                 |

## Valores predeterminados de Negocio y Colección

En lugar de pasar parámetros de prorrateo en cada cambio de plan, puedes configurar el **comportamiento de actualización y degradación predeterminado** una vez a nivel de negocio. Estos valores predeterminados se aplican a todos los cambios de plan en el portal del cliente y se pueden **sobrescribir por colección de productos**.

Existen valores predeterminados separados para actualizaciones y degradaciones:

| Configuración                 | Campo (actualización / degradación)                                         | Predeterminado (actualización) | Predeterminado (degradación) |
| ----------------------------- | --------------------------------------------------------------------------- | ------------------------------ | ---------------------------- |
| Cuándo comienza el nuevo plan | `effective_at_on_upgrade` / `effective_at_on_downgrade`                     | `immediately`                  | `next_billing_date`          |
| Cómo se cobra al cliente      | `proration_billing_mode_on_upgrade` / `proration_billing_mode_on_downgrade` | `difference_immediately`       | `difference_immediately`     |
| Si el pago del cliente falla  | `on_payment_failure`                                                        | `apply_change`                 | `apply_change`               |

Configura los predeterminados de negocio en **Ajustes → Suscripciones** y los reemplazos de colección en cada colección de productos. Cada campo de colección es independiente: déjalo sin configurar para **heredar del valor predeterminado del negocio**, o establece un valor para sobrescribirlo solo para esa colección.

### Orden de resolución

Para cualquier cambio de plan, cada configuración se resuelve en este orden:

```
per-request value (Change Plan API) → collection field (if set) → business field → system default
```

<Info>
  Un valor pasado explícitamente a la [API de Cambio de Plan](/api-reference/subscriptions/change-plan) siempre prevalecerá. Los valores predeterminados de negocio y colección solo entran en efecto cuando no se proporciona un valor explícito, lo cual ocurre en todos los cambios de plan iniciados desde el portal del cliente.
</Info>

<Tip>
  Una configuración común: mantén las actualizaciones `immediately` + `difference_immediately` para que los clientes paguen la diferencia y obtengan acceso de inmediato, y mantén las degradaciones en `next_billing_date` para que los clientes conserven su plan actual hasta que termine el ciclo.
</Tip>

## Manejo de webhooks

Rastrea el estado de la suscripción a través de webhooks para confirmar cambios de plan y pagos.

### Tipos de eventos a manejar

* `subscription.active`: suscripción activada
* `subscription.plan_changed`: plan de suscripción cambiado (cambios de actualización/degradación/addon)
* `subscription.on_hold`: cargo fallido, suscripción pausada
* `subscription.renewed`: renovación exitosa
* `payment.succeeded`: pago por cambio de plan o renovación exitoso
* `payment.failed`: pago fallido

<Info>
  Recomendamos dirigir la lógica empresarial a partir de eventos de suscripción y usar eventos de pago para confirmación y conciliación.
</Info>

### Verificar firmas y manejar intenciones

<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>
  Para esquemas de carga útil detallados, consulta <a href="/developer-resources/webhooks/intents/subscription">Cargas útiles de webhook de suscripción</a> y <a href="/developer-resources/webhooks/intents/payment">Cargas útiles de webhook de pago</a>.
</Note>

## Mejores Prácticas

Sigue estas recomendaciones para cambios confiables en los planes de suscripción:

### Estrategia de Cambio de Plan

* **Prueba a fondo**: Siempre prueba los cambios de plan en modo de prueba antes de producción
* **Elige el prorrateo con cuidado**: Selecciona el modo de prorrateo que se alinea con tu modelo de negocio
* **Maneja fallas con gracia**: Implementa un manejo adecuado de errores y lógica de reintento
* **Monitorea tasas de éxito**: Rastrea las tasas de éxito/falla de cambios de plan e investiga problemas

### Implementación de Webhook

* **Verifica firmas**: Valida siempre las firmas de los webhooks para asegurar su autenticidad
* **Implementa idempotencia**: Lidia con los eventos de webhook duplicados con gracia
* **Procesa asincrónicamente**: No bloquees las respuestas de webhook con operaciones pesadas
* **Registra todo**: Mantén registros detallados para depuración y auditoría

### Experiencia del Usuario

* **Comunica claramente**: Informa a los clientes sobre los cambios de facturación y el tiempo
* **Proporciona confirmaciones**: Envía confirmaciones por correo electrónico para cambios de plan exitosos
* **Maneja casos extremos**: Considera períodos de prueba, prorrateos y pagos fallidos
* **Actualiza la interfaz de usuario inmediatamente**: Refleja los cambios de plan en la interfaz de tu aplicación

## Problemas Comunes y Soluciones

Resuelve problemas típicos encontrados durante los cambios de plan de suscripción:

<AccordionGroup>
  <Accordion title="Charge created but subscription not updated">
    **Síntomas**: La llamada a la API tiene éxito, pero la suscripción permanece en el plan antiguo

    **Causas comunes**:

    * El procesamiento del webhook falló o se retrasó
    * El estado de la aplicación no se actualizó después de recibir webhooks
    * Problemas de transacciones en la base de datos durante la actualización del estado

    **Soluciones**:

    * Implementa un manejo robusto de webhooks con lógica de reintento
    * Usa operaciones idempotentes para actualizaciones de estado
    * Agrega monitoreo para detectar y alertar sobre eventos de webhook perdidos
    * Verifica que la URL del webhook sea accesible y responda correctamente
  </Accordion>

  <Accordion title="Credits not applied after downgrade">
    **Síntomas**: El cliente se degrada pero no ve saldo de crédito

    **Causas comunes**:

    * Expectativas del modo de prorrateo: las degradaciones acreditan la diferencia de precio completo del plan con `difference_immediately`, mientras que `prorated_immediately` crea un crédito prorrateado basado en el tiempo restante en el ciclo
    * Los créditos son específicos de suscripción y no se transfieren entre suscripciones
    * Saldo de crédito no visible en el panel del cliente

    **Soluciones**:

    * Usa `difference_immediately` para degradaciones cuando quieras créditos automáticos
    * Explica a los clientes que los créditos se aplican a futuras renovaciones de la misma suscripción
    * Implementa un portal del cliente para mostrar saldos de crédito
    * Verifica la vista previa de la próxima factura para ver los créditos aplicados
  </Accordion>

  <Accordion title="Webhook signature verification fails">
    **Síntomas**: Eventos de webhook rechazados debido a una firma inválida

    **Causas comunes**:

    * Clave secreta del webhook incorrecta
    * Cuerpo de la solicitud cruda modificado antes de la verificación de la firma
    * Algoritmo de verificación de firma incorrecto

    **Soluciones**:

    * Verifica que estás usando el correcto `DODO_WEBHOOK_SECRET` desde el panel
    * Lee el cuerpo de la solicitud cruda antes de cualquier middleware de análisis JSON
    * Usa la biblioteca estándar de verificación de webhooks para tu plataforma
    * Prueba la verificación de firmas de webhooks en el entorno de desarrollo
  </Accordion>

  <Accordion title="Plan change fails with 422 error">
    **Síntomas**: La API devuelve un error 422 Unprocessable Entity

    **Causas comunes**:

    * ID de suscripción o producto inválido
    * Suscripción no está en estado activo
    * Faltan parámetros requeridos
    * Producto no disponible para cambios de plan

    **Soluciones**:

    * Verifica que la suscripción existe y está activa
    * Comprueba que el ID del producto sea válido y esté disponible
    * Asegúrate de que todos los parámetros requeridos están proporcionados
    * Revisa la documentación de la API para los requisitos de los parámetros
  </Accordion>

  <Accordion title="Immediate charge fails during plan change">
    **Síntomas**: El cambio de plan se inicia pero el cobro inmediato falla

    **Causas comunes**:

    * Fondos insuficientes en el método de pago del cliente
    * Método de pago vencido o inválido
    * El banco rechazó la transacción
    * La detección de fraude bloqueó el cargo

    **Soluciones**:

    * Maneja eventos de webhook `payment.failed` apropiadamente
    * Notifica al cliente para actualizar el método de pago
    * Implementa lógica de reintento para fallas temporales
    * Considera permitir cambios de plan con cargos inmediatos fallidos
  </Accordion>

  <Accordion title="Subscription on hold after plan change">
    **Síntomas**: Falla el cobro del cambio de plan y la suscripción se mueve a estado `on_hold`

    **Qué sucede**:
    Cuando falla un cobro de cambio de plan, la suscripción se coloca automáticamente en estado `on_hold`. La suscripción no se renovará automáticamente hasta que se actualice el método de pago.

    **Solución**: Actualiza el método de pago para reactivar la suscripción

    Para reactivar una suscripción desde el estado `on_hold` después de un fallo en el cambio de plan:

    1. **Actualiza el método de pago** usando la API de Actualización de Método de Pago
    2. **Creación automática de cargos**: La API crea automáticamente un cargo por los adeudos pendientes
    3. **Generación de factura**: Se genera una factura por el cargo
    4. **Procesamiento de pagos**: El pago se procesa usando el nuevo método de pago
    5. **Reactivación**: Una vez realizado el pago con éxito, la suscripción se reactiva al estado `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>

    **Eventos de webhook a monitorear**:

    * `subscription.on_hold`: Suscripción en espera (recibido cuando falla el cobro del cambio de plan)
    * `payment.succeeded`: Pago exitoso de adeudos pendientes (después de actualizar el método de pago)
    * `subscription.active`: Suscripción reactivada tras pago exitoso

    **Mejores prácticas**:

    * Notifica a los clientes inmediatamente cuando falla un cobro de cambio de plan
    * Proporciona instrucciones claras sobre cómo actualizar su método de pago
    * Monitorea eventos de webhook para rastrear el estado de reactivación
    * Considera implementar lógica de reintento automático para fallos temporales de pago

    <Card title="Update Payment Method API Reference" icon="code" href="/api-reference/subscriptions/update-payment-method">
      Consulta la documentación completa de la API para actualización de métodos de pago y reactivación de suscripciones.
    </Card>
  </Accordion>
</AccordionGroup>

## Probando Tu Implementación

Sigue estos pasos para probar exhaustivamente la implementación del cambio de plan de suscripción:

<Steps>
  <Step title="Set up test environment">
    * Usa claves de API de prueba y productos de prueba
    * Crea suscripciones de prueba con diferentes tipos de plan
    * Configura el endpoint de webhook de prueba
    * Configura monitoreo y registros
  </Step>

  <Step title="Test different proration modes">
    * Prueba `prorated_immediately` con varias posiciones del ciclo de facturación
    * Prueba `difference_immediately` para actualizaciones y degradaciones
    * Prueba `full_immediately` para restablecer ciclos de facturación
    * Prueba `do_not_bill` para cambios de plan sin cargo/sin crédito
    * Verifica que los cálculos de crédito sean correctos
  </Step>

  <Step title="Test webhook handling">
    * Verifica que se reciban todos los eventos de webhook relevantes
    * Prueba la verificación de firmas de los webhooks
    * Maneja eventos de webhook duplicados con gracia
    * Prueba escenarios de fallos en el procesamiento de webhooks
  </Step>

  <Step title="Test error scenarios">
    * Prueba con IDs de suscripción inválidos
    * Prueba con métodos de pago vencidos
    * Prueba fallos de red y tiempos de espera
    * Prueba con fondos insuficientes
  </Step>

  <Step title="Monitor in production">
    * Configura alertas para cambios de plan fallidos
    * Monitorea los tiempos de procesamiento de webhooks
    * Rastrea las tasas de éxito de los cambios de plan
    * Revisa los tickets de soporte al cliente por problemas de cambio de plan
  </Step>
</Steps>

## Manejo de Errores

Maneja con gracia los errores comunes de la API en tu implementación:

### Códigos de Estado HTTP

<AccordionGroup>
  <Accordion title="200 OK">
    Solicitud de cambio de plan procesada exitosamente. La suscripción está siendo actualizada y el procesamiento de pagos ha comenzado.
  </Accordion>

  <Accordion title="400 Bad Request">
    Parámetros de solicitud inválidos. Verifica que todos los campos requeridos estén proporcionados y correctamente formateados.
  </Accordion>

  <Accordion title="401 Unauthorized">
    Clave de API inválida o faltante. Verifica que tu `DODO_PAYMENTS_API_KEY` sea correcta y tenga los permisos adecuados.
  </Accordion>

  <Accordion title="404 Not Found">
    ID de suscripción no encontrada o no pertenece a tu cuenta.
  </Accordion>

  <Accordion title="422 Unprocessable Entity">
    La suscripción no puede ser cambiada (por ejemplo, ya cancelada, producto no disponible, etc.).
  </Accordion>

  <Accordion title="500 Internal Server Error">
    Ocurrió un error en el servidor. Reintenta la solicitud después de un breve retraso.
  </Accordion>
</AccordionGroup>

### Formato de Respuesta de Error

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

## Próximos pasos

* Revisa la <a href="/api-reference/subscriptions/change-plan">API de Cambio de Plan</a>

* Explora <a href="/features/credit-based-billing">Facturación Basada en Créditos</a>

* Implementa alertas para `subscription.on_hold`

* Consulta nuestra <a href="/developer-resources/webhooks">Guía de Integración de Webhooks</a>

* Review the <a href="/api-reference/subscriptions/change-plan">Change Plan API</a>

* Explore <a href="/features/credit-based-billing">Credit-Based Billing</a>

* Implement alerts for `subscription.on_hold`

* Check out our <a href="/developer-resources/webhooks">Webhook Integration Guide</a>
