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

# Subscriptions

> Create and manage recurring subscriptions with automatic billing, plan changes, and proration

## Overview

Create and manage recurring subscriptions with automatic billing. Handle plan changes, cancellations, and invoice management seamlessly.

<CardGroup cols={2}>
  <Card title="Recurring Billing" icon="repeat">
    Automatic billing on schedule with retry logic for failed payments
  </Card>

  <Card title="Flexible Plans" icon="layer-group">
    Multiple billing intervals (monthly, yearly) with customizable pricing
  </Card>

  <Card title="Plan Changes" icon="arrows-rotate">
    Upgrade or downgrade with automatic proration handling
  </Card>

  <Card title="Invoice Management" icon="file-invoice">
    Track all subscription invoices and payment history
  </Card>
</CardGroup>

***

# Subscription Plans

Subscription plans define the pricing, billing frequency, and trial periods for your subscriptions. Create and manage plans before subscribing customers.

## Create Plan

Create a new subscription plan with pricing and billing configuration.

```bash cURL theme={null}
curl -X POST https://api.sandbox.cheqpay.mx/pos/v1/plans \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-merchant-id: YOUR_MERCHANT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pro Plan",
    "description": "Full access to all premium features",
    "amount": 29999,
    "currency": "MXN",
    "interval": "MONTH",
    "intervalCount": 1,
    "trialDays": 14,
    "externalId": "pro-monthly",
    "metadata": {
      "features": "unlimited",
      "tier": "premium"
    }
  }'
```

### Request Parameters

| Parameter       | Type              | Required | Description                                             |
| --------------- | ----------------- | -------- | ------------------------------------------------------- |
| `name`          | string (1-255)    | Yes      | Plan display name                                       |
| `description`   | string (max 1000) | No       | Plan description                                        |
| `amount`        | integer           | Yes      | Price in smallest currency unit (cents)                 |
| `currency`      | string            | Yes      | Currency code: `MXN`, `USD`                             |
| `interval`      | string            | Yes      | Billing frequency: `DAY`, `WEEK`, `MONTH`, `YEAR`       |
| `intervalCount` | integer           | No       | Number of intervals between billings (1-12, default: 1) |
| `trialDays`     | integer           | No       | Trial period in days (1-365)                            |
| `externalId`    | string (max 255)  | No       | Your internal plan identifier                           |
| `metadata`      | object            | No       | Custom key-value pairs                                  |

### Billing Intervals

| Interval | Interval Count | Example                             |
| -------- | -------------- | ----------------------------------- |
| `MONTH`  | 1              | Bill every month                    |
| `MONTH`  | 3              | Bill every 3 months (quarterly)     |
| `MONTH`  | 6              | Bill every 6 months (semi-annually) |
| `YEAR`   | 1              | Bill annually                       |
| `WEEK`   | 2              | Bill every 2 weeks (bi-weekly)      |
| `DAY`    | 7              | Bill every 7 days (weekly)          |

<Note>
  **Amount format**: Always use the smallest currency unit. For MXN/USD, that's cents.

  * \$99.99 MXN = `9999`
  * \$299.99 USD = `29999`
</Note>

### Response

```json theme={null}
{
  "id": "plan_abc123def456",
  "merchantId": "mer_xyz789",
  "externalId": "pro-monthly",
  "name": "Pro Plan",
  "description": "Full access to all premium features",
  "amount": "29999",
  "currency": "MXN",
  "interval": "MONTH",
  "intervalCount": 1,
  "trialDays": 14,
  "active": true,
  "metadata": {
    "features": "unlimited",
    "tier": "premium"
  },
  "createdAt": "2026-01-30T10:00:00.000Z",
  "updatedAt": "2026-01-30T10:00:00.000Z"
}
```

## Get Plan

Retrieve details about a specific plan.

```bash cURL theme={null}
curl -X GET https://api.sandbox.cheqpay.mx/pos/v1/plans/plan_abc123 \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-merchant-id: YOUR_MERCHANT_ID"
```

### Response

Same structure as Create Plan response.

## List Plans

List all subscription plans with filtering and pagination.

```bash cURL theme={null}
curl -X GET "https://api.sandbox.cheqpay.mx/pos/v1/plans?limit=20&page=1&active=true&interval=MONTH" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-merchant-id: YOUR_MERCHANT_ID"
```

### Query Parameters

| Parameter  | Type    | Default | Description                                                |
| ---------- | ------- | ------- | ---------------------------------------------------------- |
| `limit`    | integer | 20      | Number of results per page (1-100)                         |
| `page`     | integer | 1       | Page number                                                |
| `active`   | boolean | -       | Filter by active status                                    |
| `interval` | string  | -       | Filter by billing interval: `DAY`, `WEEK`, `MONTH`, `YEAR` |

### Response

```json theme={null}
{
  "data": [
    {
      "id": "plan_abc123",
      "name": "Pro Plan",
      "amount": "29999",
      "currency": "MXN",
      "interval": "MONTH",
      "intervalCount": 1,
      "trialDays": 14,
      "active": true
    },
    {
      "id": "plan_def456",
      "name": "Basic Plan",
      "amount": "9999",
      "currency": "MXN",
      "interval": "MONTH",
      "intervalCount": 1,
      "trialDays": null,
      "active": true
    }
  ],
  "pagination": {
    "total": 5,
    "limit": 20,
    "page": 1,
    "hasMore": false
  }
}
```

## Update Plan

Update an existing plan's settings.

```bash cURL theme={null}
curl -X PATCH https://api.sandbox.cheqpay.mx/pos/v1/plans/plan_abc123 \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-merchant-id: YOUR_MERCHANT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pro Plan - Updated",
    "description": "Now with even more features",
    "trialDays": 30,
    "active": true,
    "metadata": {
      "features": "unlimited_plus",
      "tier": "premium"
    }
  }'
```

### Request Parameters

| Parameter     | Type              | Description                                         |
| ------------- | ----------------- | --------------------------------------------------- |
| `name`        | string (1-255)    | Update plan name                                    |
| `description` | string (max 1000) | Update description (set to `null` to clear)         |
| `trialDays`   | integer (1-365)   | Update trial period (set to `null` to remove trial) |
| `active`      | boolean           | Activate or deactivate plan                         |
| `metadata`    | object            | Update metadata (set to `null` to clear)            |

<Warning>
  **Cannot update**: `amount`, `currency`, `interval`, or `intervalCount`. These are immutable after creation. Create a new plan for different pricing or billing frequency.
</Warning>

<Note>
  Updating a plan affects **future subscriptions only**. Existing subscriptions keep their original plan settings unless explicitly changed.
</Note>

### Response

```json theme={null}
{
  "id": "plan_abc123",
  "name": "Pro Plan - Updated",
  "description": "Now with even more features",
  "amount": "29999",
  "currency": "MXN",
  "interval": "MONTH",
  "intervalCount": 1,
  "trialDays": 30,
  "active": true,
  "metadata": {
    "features": "unlimited_plus",
    "tier": "premium"
  },
  "updatedAt": "2026-01-30T15:30:00.000Z"
}
```

## Archive Plan

Deactivate a plan to prevent new subscriptions.

```bash cURL theme={null}
curl -X DELETE https://api.sandbox.cheqpay.mx/pos/v1/plans/plan_abc123 \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-merchant-id: YOUR_MERCHANT_ID"
```

<Note>
  Archiving sets `active: false`. Existing subscriptions are **not affected** and continue billing normally. You can reactivate the plan by updating `active: true`.
</Note>

### Response

```json theme={null}
{
  "id": "plan_abc123",
  "name": "Pro Plan",
  "active": false,
  "updatedAt": "2026-01-30T16:00:00.000Z"
}
```

### When to Archive

<AccordionGroup>
  <Accordion title="Discontinuing a plan" icon="box-archive">
    Stop offering a plan to new customers while keeping existing subscriptions active.

    ```javascript theme={null}
    // Archive the plan
    await cheqpay.plans.archive('plan_old_pricing');

    // Existing subscribers keep their plan
    // New customers can't subscribe to it
    ```
  </Accordion>

  <Accordion title="Seasonal or limited offers" icon="calendar">
    Temporarily disable plans that shouldn't be available year-round.

    ```javascript theme={null}
    // After promotion ends
    await cheqpay.plans.archive('plan_summer_promo');

    // Reactivate next year
    await cheqpay.plans.update('plan_summer_promo', {
      active: true
    });
    ```
  </Accordion>
</AccordionGroup>

***

# Managing Subscriptions

## Create Subscription

Create a new subscription for a customer with a specific plan and payment method.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.sandbox.cheqpay.mx/pos/v1/subscriptions \
    -H "x-api-key: YOUR_API_KEY" \
    -H "x-merchant-id: YOUR_MERCHANT_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "customerId": "cus_abc123",
      "planId": "plan_basic_monthly",
      "paymentMethodId": "pm_xyz789",
      "securityCode": "123",
      "metadata": {
        "source": "website",
        "campaign": "spring_promo"
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const subscription = await cheqpay.subscriptions.create({
    customerId: 'cus_abc123',
    planId: 'plan_basic_monthly',
    paymentMethodId: 'pm_xyz789',
    securityCode: '123',
    metadata: {
      source: 'website',
      campaign: 'spring_promo'
    }
  });
  ```

  ```python Python theme={null}
  subscription = cheqpay.subscriptions.create(
    customer_id='cus_abc123',
    plan_id='plan_basic_monthly',
    payment_method_id='pm_xyz789',
    security_code='123',
    metadata={
      'source': 'website',
      'campaign': 'spring_promo'
    }
  )
  ```
</CodeGroup>

### Request Parameters

| Parameter         | Type          | Required | Description                                 |
| ----------------- | ------------- | -------- | ------------------------------------------- |
| `customerId`      | string (UUID) | Yes      | ID of the customer                          |
| `planId`          | string (UUID) | Yes      | ID of the subscription plan                 |
| `paymentMethodId` | string (UUID) | Yes      | ID of the payment method to charge          |
| `securityCode`    | string        | No       | CVV/CVC for card verification (recommended) |
| `metadata`        | object        | No       | Custom key-value pairs for tracking         |

### Response

```json theme={null}
{
  "id": "sub_abc123def456",
  "merchantId": "mer_xyz789",
  "customerId": "cus_abc123",
  "planId": "plan_basic_monthly",
  "paymentMethodId": "pm_xyz789",
  "status": "ACTIVE",
  "currentPeriodStart": "2026-01-30T00:00:00.000Z",
  "currentPeriodEnd": "2026-02-30T00:00:00.000Z",
  "trialStart": null,
  "trialEnd": null,
  "cancelAtPeriodEnd": false,
  "canceledAt": null,
  "endedAt": null,
  "metadata": {
    "source": "website",
    "campaign": "spring_promo"
  },
  "createdAt": "2026-01-30T10:30:00.000Z",
  "updatedAt": "2026-01-30T10:30:00.000Z",
  "isTrialing": false,
  "nextBillingDate": "2026-02-30T00:00:00.000Z"
}
```

### Subscription Statuses

| Status     | Description                      |
| ---------- | -------------------------------- |
| `TRIALING` | In trial period, no charges yet  |
| `ACTIVE`   | Active and billing normally      |
| `PAST_DUE` | Payment failed, retrying         |
| `CANCELED` | Canceled by customer or merchant |
| `UNPAID`   | All payment retries exhausted    |

## Get Subscription

Retrieve details about a specific subscription.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET https://api.sandbox.cheqpay.mx/pos/v1/subscriptions/sub_abc123 \
    -H "x-api-key: YOUR_API_KEY" \
    -H "x-merchant-id: YOUR_MERCHANT_ID"
  ```

  ```javascript Node.js theme={null}
  const subscription = await cheqpay.subscriptions.get('sub_abc123');
  ```

  ```python Python theme={null}
  subscription = cheqpay.subscriptions.get('sub_abc123')
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "id": "sub_abc123def456",
  "merchantId": "mer_xyz789",
  "customerId": "cus_abc123",
  "planId": "plan_basic_monthly",
  "paymentMethodId": "pm_xyz789",
  "status": "ACTIVE",
  "currentPeriodStart": "2026-01-30T00:00:00.000Z",
  "currentPeriodEnd": "2026-02-30T00:00:00.000Z",
  "trialStart": null,
  "trialEnd": null,
  "cancelAtPeriodEnd": false,
  "canceledAt": null,
  "endedAt": null,
  "metadata": {
    "source": "website"
  },
  "createdAt": "2026-01-30T10:30:00.000Z",
  "updatedAt": "2026-01-30T10:30:00.000Z",
  "plan": {
    "id": "plan_basic_monthly",
    "name": "Basic Plan",
    "amount": "9999",
    "currency": "MXN",
    "interval": "MONTH",
    "intervalCount": 1
  }
}
```

## List Subscriptions

List all subscriptions with filtering and pagination.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.sandbox.cheqpay.mx/pos/v1/subscriptions?limit=20&page=1&status=ACTIVE" \
    -H "x-api-key: YOUR_API_KEY" \
    -H "x-merchant-id: YOUR_MERCHANT_ID"
  ```

  ```javascript Node.js theme={null}
  const { data, pagination } = await cheqpay.subscriptions.list({
    limit: 20,
    page: 1,
    status: 'ACTIVE',
    customerId: 'cus_abc123'
  });
  ```

  ```python Python theme={null}
  result = cheqpay.subscriptions.list(
    limit=20,
    page=1,
    status='ACTIVE',
    customer_id='cus_abc123'
  )
  ```
</CodeGroup>

### Query Parameters

| Parameter    | Type          | Default | Description                                                              |
| ------------ | ------------- | ------- | ------------------------------------------------------------------------ |
| `limit`      | integer       | 20      | Number of results per page (1-100)                                       |
| `page`       | integer       | 1       | Page number                                                              |
| `customerId` | string (UUID) | -       | Filter by customer                                                       |
| `planId`     | string (UUID) | -       | Filter by plan                                                           |
| `status`     | string        | -       | Filter by status: `TRIALING`, `ACTIVE`, `PAST_DUE`, `CANCELED`, `UNPAID` |

### Response

```json theme={null}
{
  "data": [
    {
      "id": "sub_abc123",
      "customerId": "cus_abc123",
      "planId": "plan_basic_monthly",
      "status": "ACTIVE",
      "currentPeriodStart": "2026-01-30T00:00:00.000Z",
      "currentPeriodEnd": "2026-02-30T00:00:00.000Z",
      "plan": {
        "id": "plan_basic_monthly",
        "name": "Basic Plan",
        "amount": "9999",
        "currency": "MXN"
      }
    }
  ],
  "pagination": {
    "total": 45,
    "limit": 20,
    "page": 1,
    "hasMore": true
  }
}
```

## Update Subscription

Update subscription settings like payment method or cancellation behavior.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://api.sandbox.cheqpay.mx/pos/v1/subscriptions/sub_abc123 \
    -H "x-api-key: YOUR_API_KEY" \
    -H "x-merchant-id: YOUR_MERCHANT_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "paymentMethodId": "pm_new123",
      "cancelAtPeriodEnd": true,
      "metadata": {
        "updated_reason": "customer_request"
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const subscription = await cheqpay.subscriptions.update('sub_abc123', {
    paymentMethodId: 'pm_new123',
    cancelAtPeriodEnd: true,
    metadata: {
      updated_reason: 'customer_request'
    }
  });
  ```

  ```python Python theme={null}
  subscription = cheqpay.subscriptions.update(
    subscription_id='sub_abc123',
    payment_method_id='pm_new123',
    cancel_at_period_end=True,
    metadata={'updated_reason': 'customer_request'}
  )
  ```
</CodeGroup>

### Request Parameters

| Parameter           | Type          | Description                                  |
| ------------------- | ------------- | -------------------------------------------- |
| `paymentMethodId`   | string (UUID) | Change the payment method                    |
| `cancelAtPeriodEnd` | boolean       | Cancel subscription at end of current period |
| `metadata`          | object        | Update metadata (set to `null` to clear)     |

<Note>
  At least one field must be provided. To change the plan, use the [Change Plan](#change-plan) endpoint instead.
</Note>

### Response

```json theme={null}
{
  "id": "sub_abc123",
  "customerId": "cus_abc123",
  "planId": "plan_basic_monthly",
  "paymentMethodId": "pm_new123",
  "status": "ACTIVE",
  "cancelAtPeriodEnd": true,
  "currentPeriodStart": "2026-01-30T00:00:00.000Z",
  "currentPeriodEnd": "2026-02-30T00:00:00.000Z",
  "metadata": {
    "updated_reason": "customer_request"
  },
  "updatedAt": "2026-01-30T15:45:00.000Z"
}
```

## Cancel Subscription

Cancel a subscription immediately or at the end of the current billing period.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.sandbox.cheqpay.mx/pos/v1/subscriptions/sub_abc123/cancel \
    -H "x-api-key: YOUR_API_KEY" \
    -H "x-merchant-id: YOUR_MERCHANT_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "cancelImmediately": false
    }'
  ```

  ```javascript Node.js theme={null}
  const subscription = await cheqpay.subscriptions.cancel('sub_abc123', {
    cancelImmediately: false
  });
  ```

  ```python Python theme={null}
  subscription = cheqpay.subscriptions.cancel(
    subscription_id='sub_abc123',
    cancel_immediately=False
  )
  ```
</CodeGroup>

### Request Parameters

| Parameter           | Type    | Default | Description                                                      |
| ------------------- | ------- | ------- | ---------------------------------------------------------------- |
| `cancelImmediately` | boolean | false   | If `true`, cancel immediately. If `false`, cancel at period end. |

### Cancellation Behaviors

<AccordionGroup>
  <Accordion title="Cancel at Period End (Recommended)" icon="calendar-xmark">
    Customer keeps access until their billing period ends. No refunds issued.

    * Status remains `ACTIVE`
    * `cancelAtPeriodEnd` set to `true`
    * `canceledAt` timestamp recorded
    * Billing continues until period end
    * Status changes to `CANCELED` after period expires
  </Accordion>

  <Accordion title="Cancel Immediately" icon="xmark">
    Customer loses access immediately. No refunds issued.

    * Status immediately set to `CANCELED`
    * `canceledAt` and `endedAt` timestamps recorded
    * No further billing
    * Customer loses access now
  </Accordion>
</AccordionGroup>

### Response

```json theme={null}
{
  "id": "sub_abc123",
  "customerId": "cus_abc123",
  "status": "ACTIVE",
  "cancelAtPeriodEnd": true,
  "canceledAt": "2026-01-30T16:00:00.000Z",
  "endedAt": null,
  "currentPeriodEnd": "2026-02-30T00:00:00.000Z"
}
```

## List Subscription Invoices

Get all invoices for a specific subscription.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.sandbox.cheqpay.mx/pos/v1/subscriptions/sub_abc123/invoices?limit=20&page=1" \
    -H "x-api-key: YOUR_API_KEY" \
    -H "x-merchant-id: YOUR_MERCHANT_ID"
  ```

  ```javascript Node.js theme={null}
  const { data, pagination } = await cheqpay.subscriptions.listInvoices('sub_abc123', {
    limit: 20,
    page: 1
  });
  ```

  ```python Python theme={null}
  result = cheqpay.subscriptions.list_invoices(
    subscription_id='sub_abc123',
    limit=20,
    page=1
  )
  ```
</CodeGroup>

### Query Parameters

| Parameter | Type    | Default | Description                        |
| --------- | ------- | ------- | ---------------------------------- |
| `limit`   | integer | 20      | Number of results per page (1-100) |
| `page`    | integer | 1       | Page number                        |

### Response

```json theme={null}
{
  "data": [
    {
      "id": "inv_abc123",
      "subscriptionId": "sub_abc123",
      "paymentOrderId": "ord_xyz789",
      "amount": "9999",
      "baseAmount": "9999",
      "prorationAmount": null,
      "currency": "MXN",
      "status": "PAID",
      "periodStart": "2026-01-30T00:00:00.000Z",
      "periodEnd": "2026-02-30T00:00:00.000Z",
      "dueDate": "2026-01-30T00:00:00.000Z",
      "paidAt": "2026-01-30T10:35:00.000Z",
      "attemptCount": 1,
      "maxRetries": 3,
      "nextRetryAt": null,
      "lastFailureReason": null,
      "metadata": null,
      "createdAt": "2026-01-30T10:30:00.000Z"
    }
  ],
  "pagination": {
    "total": 3,
    "limit": 20,
    "page": 1,
    "hasMore": false
  }
}
```

### Invoice Statuses

| Status           | Description                            |
| ---------------- | -------------------------------------- |
| `DRAFT`          | Invoice created but not yet finalized  |
| `OPEN`           | Ready for payment, not yet attempted   |
| `PAID`           | Successfully paid                      |
| `PAYMENT_FAILED` | Payment attempt failed, will retry     |
| `UNCOLLECTIBLE`  | All retries exhausted, won't try again |
| `VOID`           | Invoice voided/canceled                |

***

# Plan Changes & Proration

When a customer changes their subscription plan mid-cycle, Cheqpay calculates a fair adjustment (proration) so they only pay for what they use.

<CardGroup cols={3}>
  <Card title="Upgrades" icon="arrow-up">
    Charged immediately or added to next invoice, depending on your proration behavior
  </Card>

  <Card title="Downgrades" icon="arrow-down">
    Always deferred to the next billing cycle. No immediate refund or charge.
  </Card>

  <Card title="Preview" icon="eye">
    See exact costs before committing with the preview endpoint
  </Card>
</CardGroup>

## How Proration Works

When a customer upgrades mid-cycle, Cheqpay calculates:

1. **Credit** for the unused portion of the old plan
2. **Charge** for the prorated portion of the new plan
3. **Net amount** = Charge - Credit

The calculation uses daily proration based on the billing period:

```
Daily Rate (old) = old_plan_amount / total_days_in_period
Daily Rate (new) = new_plan_amount / total_days_in_period
Days Remaining   = days from change date to period end

Credit = Daily Rate (old) x Days Remaining
Charge = Daily Rate (new) x Days Remaining
Net    = Charge - Credit
```

<Note>
  All amounts are in the smallest currency unit (cents). A net amount of `5000` means \$50.00 MXN.
</Note>

Example: Customer on Basic Plan (\$99.99/mo) upgrades to Pro Plan (\$299.99/mo) on day 15 of a 30-day cycle:

* Credit for unused Basic: \$99.99 x (15/30) = \$50.00
* Charge for prorated Pro: \$299.99 x (15/30) = \$150.00
* **Net proration: \$100.00** (charged or deferred, depending on behavior)

## Change Plan

Update a subscription to a different plan with automatic proration handling.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://api.sandbox.cheqpay.mx/pos/v1/subscriptions/sub_abc123/plan \
    -H "x-api-key: YOUR_API_KEY" \
    -H "x-merchant-id: YOUR_MERCHANT_ID" \
    -H "x-idempotency-key: unique-change-key-123" \
    -H "Content-Type: application/json" \
    -d '{
      "newPlanId": "plan_pro_monthly",
      "prorationBehavior": "always_invoice"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await cheqpay.subscriptions.changePlan('sub_abc123', {
    newPlanId: 'plan_pro_monthly',
    prorationBehavior: 'always_invoice'
  });
  ```

  ```python Python theme={null}
  response = cheqpay.subscriptions.change_plan(
    subscription_id='sub_abc123',
    new_plan_id='plan_pro_monthly',
    proration_behavior='always_invoice'
  )
  ```
</CodeGroup>

### Request Parameters

| Parameter           | Type              | Required | Description                                                                                               |
| ------------------- | ----------------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `newPlanId`         | string (UUID)     | Yes      | ID of the plan to switch to                                                                               |
| `prorationBehavior` | string            | No       | How to handle proration (see below). Defaults to your merchant configuration.                             |
| `prorationDate`     | string (ISO 8601) | No       | Custom date to calculate proration from. Must be within the current billing period. Defaults to now.      |
| `resetBillingCycle` | boolean           | No       | Reset the billing cycle to start from today. Only works with `always_invoice` upgrades. Default: `false`. |

### Headers

| Header              | Required    | Description                                                                                                                                              |
| ------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `x-idempotency-key` | Recommended | Unique key to prevent duplicate plan changes. If a request with the same key was already processed, the original result is returned. Valid for 24 hours. |

### Proration Behaviors

<AccordionGroup>
  <Accordion title="create_prorations (Default)" icon="clock">
    **Deferred to next invoice**

    The proration is calculated and stored on the subscription. When the next billing cycle runs, the proration amount is added to (or subtracted from) the invoice.

    * No immediate charge to the customer
    * Proration appears as an adjustment on the next invoice
    * Plan switches immediately, but billing adjustment is deferred
    * Supports accumulation: if a customer changes plans multiple times in the same cycle, all prorations combine into a single adjustment

    **Best for**: Most plan changes, especially when you want a smooth customer experience without mid-cycle charges.

    ```json theme={null}
    // Response
    {
      "proration": {
        "behavior": "create_prorations",
        "netAmount": "5000",
        "appliedOn": "next_invoice",
        "nextInvoiceDate": "2026-02-15T00:00:00.000Z",
        "nextInvoiceAmount": "34999"
      }
    }
    ```

    **What the next invoice looks like:**

    ```
    Pro Plan (monthly)        \$299.99
    Proration adjustment      + \$50.00
    ─────────────────────────────────
    Total                     \$349.99
    ```
  </Accordion>

  <Accordion title="always_invoice" icon="bolt">
    **Charge immediately**

    Creates a proration invoice and processes payment right away. The entire operation (invoice creation + payment + subscription update) is atomic — if the payment fails, nothing changes.

    * Immediate charge via the customer's payment method on file
    * A proration invoice is created and paid instantly
    * If payment fails, the plan change is rejected (nothing changes)
    * Optionally reset the billing cycle with `resetBillingCycle: true`

    **Best for**: Upgrades where you want to collect payment right away.

    ```json theme={null}
    // Response
    {
      "proration": {
        "behavior": "always_invoice",
        "netAmount": "5000",
        "appliedOn": "immediate",
        "invoiceId": "inv_proration_xyz",
        "chargedAmount": "5000",
        "chargedAt": "2026-01-30T10:30:00.000Z",
        "billingCycleReset": false
      }
    }
    ```

    <Warning>
      If the payment fails, the entire operation is rolled back. The subscription stays on the old plan, no invoice is created.
    </Warning>
  </Accordion>

  <Accordion title="none" icon="forward">
    **No proration**

    Switch plans immediately without any proration calculation. The customer pays the full new plan amount at the next billing date, regardless of when they switched.

    * No credits or charges for unused time
    * Plan switches immediately
    * Next invoice at full new plan price

    **Best for**: Trial conversions, promotional plan switches, or situations where proration isn't desired.

    ```json theme={null}
    // Response
    {
      "proration": null
    }
    ```
  </Accordion>
</AccordionGroup>

### Upgrade vs Downgrade Behavior

| Direction                           | What happens                                                                                                  | Proration calculated?                                               |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| **Upgrade** (new plan costs more)   | Plan changes immediately. Proration applied based on your `prorationBehavior`.                                | Yes (for `create_prorations` and `always_invoice`)                  |
| **Downgrade** (new plan costs less) | Plan change is **deferred** to the next billing cycle. Customer keeps the current plan until the period ends. | No. Downgrade is always deferred regardless of `prorationBehavior`. |
| **Same price**                      | Treated as an upgrade. Plan changes immediately.                                                              | Yes                                                                 |

<Warning>
  **Downgrades are always deferred.** Even if you send `prorationBehavior: "always_invoice"`, a downgrade will be deferred to the next billing cycle. This ensures customers keep access to the plan they already paid for.
</Warning>

### Billing Cycle Reset

When using `always_invoice`, you can optionally reset the billing cycle:

```bash cURL theme={null}
curl -X PATCH https://api.sandbox.cheqpay.mx/pos/v1/subscriptions/sub_abc123/plan \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-merchant-id: YOUR_MERCHANT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "newPlanId": "plan_pro_monthly",
    "prorationBehavior": "always_invoice",
    "resetBillingCycle": true
  }'
```

With `resetBillingCycle: true`:

* The billing period restarts from today
* The customer is charged the **full new plan amount minus credit for unused old plan time**
* The next invoice date is recalculated from today

<Note>
  `resetBillingCycle` is only allowed with `always_invoice` behavior on upgrades. It will be rejected for downgrades, `create_prorations`, or `none`.
</Note>

### Response

```json theme={null}
{
  "subscription": {
    "id": "sub_abc123",
    "merchantId": "mer_xyz789",
    "customerId": "cus_customer123",
    "planId": "plan_pro_monthly",
    "paymentMethodId": "pm_xyz789",
    "status": "ACTIVE",
    "currentPeriodStart": "2026-01-15T00:00:00.000Z",
    "currentPeriodEnd": "2026-02-15T00:00:00.000Z",
    "cancelAtPeriodEnd": false,
    "createdAt": "2026-01-01T10:30:00.000Z",
    "updatedAt": "2026-01-30T10:30:00.000Z"
  },
  "proration": {
    "behavior": "always_invoice",
    "netAmount": "5000",
    "appliedOn": "immediate",
    "invoiceId": "inv_proration_xyz",
    "chargedAmount": "5000",
    "chargedAt": "2026-01-30T10:30:00.000Z",
    "nextInvoiceDate": "2026-02-15T00:00:00.000Z",
    "nextInvoiceAmount": "29999",
    "billingCycleReset": false
  }
}
```

**Response for a downgrade:**

```json theme={null}
{
  "subscription": {
    "id": "sub_abc123",
    "planId": "plan_pro_monthly",
    "status": "ACTIVE"
  },
  "proration": {
    "behavior": "create_prorations",
    "netAmount": null,
    "appliedOn": "deferred"
  }
}
```

<Note>
  For downgrades, the `subscription.planId` in the response still shows the **current** plan. The new (cheaper) plan will take effect when the next billing cycle starts.
</Note>

## Preview Plan Change

See the exact impact of a plan change before committing. This endpoint performs all calculations without modifying the subscription.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.sandbox.cheqpay.mx/pos/v1/subscriptions/sub_abc123/preview-plan-change \
    -H "x-api-key: YOUR_API_KEY" \
    -H "x-merchant-id: YOUR_MERCHANT_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "newPlanId": "plan_pro_monthly",
      "prorationBehavior": "always_invoice"
    }'
  ```

  ```javascript Node.js theme={null}
  const preview = await cheqpay.subscriptions.previewPlanChange('sub_abc123', {
    newPlanId: 'plan_pro_monthly',
    prorationBehavior: 'always_invoice'
  });

  if (preview.immediateCharge) {
    console.log(`Immediate charge: $${(preview.immediateCharge.amount / 100).toFixed(2)}`);
  }
  console.log(`Next invoice: $${(preview.nextInvoice.totalAmount / 100).toFixed(2)} on ${preview.nextInvoice.date}`);
  ```

  ```python Python theme={null}
  preview = cheqpay.subscriptions.preview_plan_change(
    subscription_id='sub_abc123',
    new_plan_id='plan_pro_monthly',
    proration_behavior='always_invoice'
  )
  ```
</CodeGroup>

### Request Parameters

Same as [Change Plan](#request-parameters-5) — `newPlanId`, `prorationBehavior`, `prorationDate`, `resetBillingCycle`.

### Response

```json theme={null}
{
  "currentPlan": {
    "id": "plan_basic_monthly",
    "name": "Basic Plan",
    "amount": "9999",
    "currency": "MXN",
    "interval": "MONTH",
    "intervalCount": 1
  },
  "newPlan": {
    "id": "plan_pro_monthly",
    "name": "Pro Plan",
    "amount": "29999",
    "currency": "MXN",
    "interval": "MONTH",
    "intervalCount": 1
  },
  "direction": "upgrade",
  "billingPeriod": {
    "start": "2026-01-15T00:00:00.000Z",
    "end": "2026-02-15T00:00:00.000Z",
    "daysRemaining": 16,
    "totalDays": 31
  },
  "proration": {
    "behavior": "always_invoice",
    "creditAmount": "5161",
    "chargeAmount": "15484",
    "netAmount": "10323",
    "percentageRemaining": 51.61
  },
  "immediateCharge": {
    "amount": "10323",
    "description": "Prorated charge for plan upgrade"
  },
  "nextInvoice": {
    "date": "2026-02-15T00:00:00.000Z",
    "baseAmount": "29999",
    "prorationAdjustment": null,
    "totalAmount": "29999"
  }
}
```

**Preview for `create_prorations`:**

```json theme={null}
{
  "direction": "upgrade",
  "proration": {
    "behavior": "create_prorations",
    "creditAmount": "5161",
    "chargeAmount": "15484",
    "netAmount": "10323",
    "percentageRemaining": 51.61,
    "existingPendingProration": null,
    "accumulatedNetAmount": "10323"
  },
  "immediateCharge": null,
  "nextInvoice": {
    "date": "2026-02-15T00:00:00.000Z",
    "baseAmount": "29999",
    "prorationAdjustment": "10323",
    "totalAmount": "40322"
  }
}
```

<Tip>
  The `existingPendingProration` and `accumulatedNetAmount` fields appear when using `create_prorations`. If the customer already has a pending proration from a previous plan change in the same cycle, these show the accumulated total.
</Tip>

### Use Cases

<AccordionGroup>
  <Accordion title="Show upgrade cost to customer" icon="receipt">
    Display the exact amount before they confirm.

    ```javascript theme={null}
    const preview = await cheqpay.subscriptions.previewPlanChange('sub_123', {
      newPlanId: 'plan_pro',
      prorationBehavior: 'always_invoice'
    });

    if (preview.immediateCharge) {
      showConfirmation(
        `You'll be charged $${(preview.immediateCharge.amount / 100).toFixed(2)} today`
      );
    }
    ```
  </Accordion>

  <Accordion title="Compare proration strategies" icon="scale-balanced">
    Let customers choose how they'd like to pay.

    ```javascript theme={null}
    const immediate = await cheqpay.subscriptions.previewPlanChange('sub_123', {
      newPlanId: 'plan_pro',
      prorationBehavior: 'always_invoice'
    });

    const deferred = await cheqpay.subscriptions.previewPlanChange('sub_123', {
      newPlanId: 'plan_pro',
      prorationBehavior: 'create_prorations'
    });

    // Option A: Pay $X now, $Y next month
    // Option B: Pay $Z next month (includes adjustment)
    ```
  </Accordion>
</AccordionGroup>

## List Plan Changes

Get the history of all plan changes for a subscription. Every plan change (upgrade, downgrade, or same-price swap) is recorded with full details.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.sandbox.cheqpay.mx/pos/v1/subscriptions/sub_abc123/plan-changes?limit=20&page=1" \
    -H "x-api-key: YOUR_API_KEY" \
    -H "x-merchant-id: YOUR_MERCHANT_ID"
  ```

  ```javascript Node.js theme={null}
  const { data, pagination } = await cheqpay.subscriptions.listPlanChanges('sub_abc123', {
    limit: 20,
    page: 1
  });
  ```

  ```python Python theme={null}
  result = cheqpay.subscriptions.list_plan_changes(
    subscription_id='sub_abc123',
    limit=20,
    page=1
  )
  ```
</CodeGroup>

### Query Parameters

| Parameter | Type    | Default | Description                        |
| --------- | ------- | ------- | ---------------------------------- |
| `limit`   | integer | 20      | Number of results per page (1-100) |
| `page`    | integer | 1       | Page number                        |

### Response

```json theme={null}
{
  "data": [
    {
      "id": "pc_def456",
      "subscriptionId": "sub_abc123",
      "fromPlanId": "plan_pro_monthly",
      "toPlanId": "plan_premium_monthly",
      "changeDate": "2026-02-10T14:30:00.000Z",
      "prorationBehavior": "always_invoice",
      "prorationAmount": "15000",
      "invoiceId": "inv_proration_abc",
      "reason": null,
      "initiatedBy": null,
      "createdAt": "2026-02-10T14:30:00.000Z"
    },
    {
      "id": "pc_abc123",
      "subscriptionId": "sub_abc123",
      "fromPlanId": "plan_basic_monthly",
      "toPlanId": "plan_pro_monthly",
      "changeDate": "2026-01-20T10:00:00.000Z",
      "prorationBehavior": "create_prorations",
      "prorationAmount": "5000",
      "invoiceId": null,
      "reason": null,
      "initiatedBy": null,
      "createdAt": "2026-01-20T10:00:00.000Z"
    }
  ],
  "pagination": {
    "total": 2,
    "limit": 20,
    "page": 1,
    "hasMore": false
  }
}
```

### Response Fields

| Field               | Type              | Description                                                                                                |
| ------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------- |
| `id`                | string            | Unique plan change record ID                                                                               |
| `subscriptionId`    | string            | The subscription this change belongs to                                                                    |
| `fromPlanId`        | string            | The plan the customer was on before the change                                                             |
| `toPlanId`          | string            | The plan the customer changed to                                                                           |
| `changeDate`        | string (ISO 8601) | When the plan change occurred                                                                              |
| `prorationBehavior` | string            | The proration strategy used: `create_prorations`, `always_invoice`, `none`, or `deferred` (for downgrades) |
| `prorationAmount`   | string            | The proration amount calculated (in cents). `"0"` for downgrades or `none` behavior.                       |
| `invoiceId`         | string or null    | ID of the proration invoice created (only for `always_invoice`)                                            |
| `reason`            | string or null    | Description of the change                                                                                  |
| `initiatedBy`       | string or null    | Who initiated the change (API key, user, etc.)                                                             |
| `createdAt`         | string (ISO 8601) | When the record was created                                                                                |

## Merchant Configuration

Your account has a default proration behavior that controls how plan changes are billed, plus a flag that controls whether individual API requests can override that default.

<Note>
  These settings are managed by Cheqpay and can't be changed through the API. To update your account's default proration behavior or whether request-level overrides are allowed, [contact Cheqpay support](mailto:support@cheqpay.mx).
</Note>

As long as overrides are enabled for your account, you don't need to change anything: just set the proration behavior per request with the `prorationBehavior` parameter, exactly as shown in the plan change examples above.

### Configuration Options

| Field                   | Type    | Default             | Description                                                                        |
| ----------------------- | ------- | ------------------- | ---------------------------------------------------------------------------------- |
| `defaultBehavior`       | string  | `create_prorations` | Default proration strategy: `create_prorations`, `always_invoice`, or `none`       |
| `allowBehaviorOverride` | boolean | `true`              | Whether the `prorationBehavior` parameter in API requests can override the default |

### Behavior Resolution

When a plan change request is made, the proration behavior is determined in this order:

1. If the request includes `prorationBehavior` **and** `allowBehaviorOverride` is `true` → use the request value
2. If the request includes `prorationBehavior` **but** `allowBehaviorOverride` is `false` → the request value is ignored, merchant default is used
3. If no `prorationBehavior` in the request → use the merchant `defaultBehavior`
4. If no merchant configuration exists → system default (`create_prorations`)

```javascript theme={null}
// Merchant config: defaultBehavior = "create_prorations", allowBehaviorOverride = false

const result = await cheqpay.subscriptions.changePlan('sub_123', {
  newPlanId: 'plan_pro',
  prorationBehavior: 'always_invoice'  // This will be IGNORED
});
// Result: create_prorations behavior is used (merchant config wins)
```

## Common Scenarios

### Upgrade with Immediate Charge

Customer upgrades and pays the difference immediately.

```javascript theme={null}
const result = await cheqpay.subscriptions.changePlan('sub_abc123', {
  newPlanId: 'plan_premium',
  prorationBehavior: 'always_invoice'
});

// Customer charged immediately for the prorated difference
// A proration invoice is created and paid
// Next invoice will be at the new plan's full price
console.log(`Charged: ${result.proration.chargedAmount}`);
console.log(`Invoice: ${result.proration.invoiceId}`);
```

### Upgrade with Deferred Proration

Customer upgrades now, pays the adjustment on the next invoice.

```javascript theme={null}
const result = await cheqpay.subscriptions.changePlan('sub_abc123', {
  newPlanId: 'plan_premium',
  prorationBehavior: 'create_prorations'
});

// Plan switches immediately
// No charge today
// Next invoice: base plan amount + proration adjustment
console.log(`Next invoice will be: ${result.proration.nextInvoiceAmount}`);
```

### Downgrade (Always Deferred)

Customer downgrades. They keep the current plan until the billing period ends.

```javascript theme={null}
const result = await cheqpay.subscriptions.changePlan('sub_abc123', {
  newPlanId: 'plan_basic'
});

// Plan does NOT change immediately
// Customer keeps the premium plan until period ends
// At next billing cycle, the plan switches and bills at the new lower rate
// proration.appliedOn = "deferred"
```

### Multiple Plan Changes in One Cycle

When using `create_prorations`, multiple changes in the same billing cycle accumulate.

```javascript theme={null}
// Change 1: Basic ($99) -> Pro ($199)
// Net proration: +$50
await cheqpay.subscriptions.changePlan('sub_abc123', {
  newPlanId: 'plan_pro',
  prorationBehavior: 'create_prorations'
});

// Change 2: Pro ($199) -> Premium ($399)
// Net proration: +$100
// Accumulated: $50 + $100 = $150
await cheqpay.subscriptions.changePlan('sub_abc123', {
  newPlanId: 'plan_premium',
  prorationBehavior: 'create_prorations'
});

// Next invoice: $399 (Premium base) + $150 (accumulated proration) = $549
```

<Note>
  When using `always_invoice`, each change is charged independently. There's no accumulation — each upgrade creates its own proration invoice.
</Note>

### Upgrade with Billing Cycle Reset

Start a fresh billing period from today after upgrading.

```javascript theme={null}
const result = await cheqpay.subscriptions.changePlan('sub_abc123', {
  newPlanId: 'plan_premium',
  prorationBehavior: 'always_invoice',
  resetBillingCycle: true
});

// Customer is charged: new plan amount - credit for unused old plan
// Billing period restarts from today
// Next billing date is recalculated
```

### Cancel a Pending Downgrade with an Upgrade

If a customer downgrades and then upgrades before the next billing cycle, the downgrade is automatically canceled.

```javascript theme={null}
// Step 1: Customer downgrades from Premium to Basic
await cheqpay.subscriptions.changePlan('sub_abc123', {
  newPlanId: 'plan_basic'
});
// Downgrade is pending, takes effect at next billing cycle

// Step 2: Customer changes their mind and upgrades to Pro
await cheqpay.subscriptions.changePlan('sub_abc123', {
  newPlanId: 'plan_pro',
  prorationBehavior: 'always_invoice'
});
// The pending downgrade is canceled
// The upgrade is processed with proration
```

## Webhooks

When a subscription plan changes, Cheqpay sends a `subscription.plan_changed` webhook event to your configured URL.

```json theme={null}
{
  "id": "evt_abc123",
  "event": "subscription.plan_changed",
  "data": {
    "subscriptionId": "sub_abc123",
    "customerId": "cus_xyz789",
    "previousPlan": {
      "id": "plan_basic",
      "name": "Basic Plan",
      "amount": "9999"
    },
    "newPlan": {
      "id": "plan_pro",
      "name": "Pro Plan",
      "amount": "29999"
    },
    "changeDirection": "upgrade",
    "proration": {
      "behavior": "always_invoice",
      "netAmount": "5000",
      "appliedOn": "immediate",
      "invoiceId": "inv_proration_xyz",
      "chargedAmount": "5000",
      "chargedAt": "2026-01-30T10:30:00.000Z",
      "billingCycleReset": false
    },
    "changedAt": "2026-01-30T10:30:00.000Z"
  }
}
```

The webhook includes an `x-webhook-signature` header for verification. See [Webhooks](/features/webhooks) for setup and verification details.

### Webhook fields

| Field                         | Description                                                                     |
| ----------------------------- | ------------------------------------------------------------------------------- |
| `changeDirection`             | `upgrade`, `downgrade`, or `same`                                               |
| `proration.behavior`          | Which proration strategy was used                                               |
| `proration.appliedOn`         | `immediate` (charged now), `next_invoice` (deferred), or `deferred` (downgrade) |
| `proration.invoiceId`         | Only present for `always_invoice` — the proration invoice created               |
| `proration.chargedAmount`     | Only present for `always_invoice` — the amount charged                          |
| `proration.nextInvoiceDate`   | Only present for `create_prorations` — when the proration will be applied       |
| `proration.nextInvoiceAmount` | Only present for `create_prorations` — the total next invoice amount            |

## Error Handling

<ResponseField name="errors" type="object[]">
  Common errors when working with subscriptions and plans
</ResponseField>

### Plan Errors

| Error Code               | Description                              | Solution                                                   |
| ------------------------ | ---------------------------------------- | ---------------------------------------------------------- |
| `plan_not_found`         | Plan doesn't exist                       | Verify plan ID is correct                                  |
| `plan_not_owned`         | Plan belongs to different merchant       | Check merchant credentials                                 |
| `invalid_amount`         | Amount is below minimum or above maximum | Amount must be between 100 and 100,000,000 cents           |
| `invalid_currency`       | Unsupported currency                     | Use MXN or USD                                             |
| `invalid_interval`       | Invalid billing interval                 | Use DAY, WEEK, MONTH, or YEAR                              |
| `invalid_interval_count` | Interval count out of range              | Must be between 1 and 12                                   |
| `invalid_trial_days`     | Trial days out of range                  | Must be between 1 and 365                                  |
| `plan_immutable_fields`  | Attempting to update immutable fields    | Cannot change amount, currency, or interval after creation |
| `no_fields_to_update`    | Update request has no fields             | Provide at least one field to update                       |
| `plan_inactive`          | Cannot subscribe to inactive plan        | Activate the plan first                                    |

### Create Subscription Errors

| Error Code                 | Description                                  | Solution                                    |
| -------------------------- | -------------------------------------------- | ------------------------------------------- |
| `customer_not_found`       | Customer doesn't exist                       | Verify customer ID is valid                 |
| `plan_not_found`           | Plan doesn't exist                           | Check plan ID is correct                    |
| `payment_method_not_found` | Payment method doesn't exist                 | Verify payment method ID                    |
| `payment_method_not_owned` | Payment method belongs to different customer | Use a payment method owned by this customer |
| `initial_payment_failed`   | First payment attempt failed                 | Check payment method is valid and has funds |

### Get/Update/Cancel Errors

| Error Code                      | Description                                | Solution                                            |
| ------------------------------- | ------------------------------------------ | --------------------------------------------------- |
| `subscription_not_found`        | Subscription doesn't exist                 | Verify subscription ID                              |
| `subscription_not_owned`        | Subscription belongs to different merchant | Check you're using the correct merchant credentials |
| `subscription_already_canceled` | Can't modify canceled subscription         | Subscription is already canceled                    |
| `no_fields_to_update`           | Update request has no fields               | Provide at least one field to update                |

### Plan Change Errors

| Error Code                      | Description                                                            | Solution                                                                                                                                          |
| ------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `subscription_not_found`        | Subscription doesn't exist                                             | Verify subscription ID                                                                                                                            |
| `plan_not_found`                | Target plan doesn't exist                                              | Check plan ID is valid                                                                                                                            |
| `same_plan`                     | New plan is same as current                                            | Choose a different plan                                                                                                                           |
| `currency_mismatch`             | New plan has a different currency than current plan                    | Both plans must use the same currency                                                                                                             |
| `subscription_not_active`       | Can't change plan on canceled or unpaid subscription                   | Only `ACTIVE` and `PAST_DUE` subscriptions can change plans                                                                                       |
| `trialing_subscription`         | Can't change plan during trial period                                  | Wait until the trial ends and the subscription becomes `ACTIVE`                                                                                   |
| `payment_failed`                | Immediate proration charge failed (always\_invoice)                    | Check the payment method is valid and has sufficient funds                                                                                        |
| `invalid_payment_method`        | Payment method missing or doesn't belong to customer                   | Required for `always_invoice` upgrades. Ensure the subscription has a valid payment method.                                                       |
| `behavior_override_not_allowed` | Merchant config doesn't allow overriding the default behavior          | Omit `prorationBehavior` from the request, or update your merchant configuration                                                                  |
| `behavior_mixing`               | Cannot use a different proration behavior in the same billing cycle    | If you already changed plans with `create_prorations`, you must use `create_prorations` for subsequent changes in the same cycle (and vice versa) |
| `invalid_proration_date`        | Custom proration date is outside the current billing period            | `prorationDate` must be between `currentPeriodStart` and `currentPeriodEnd`                                                                       |
| `invalid_reset_billing_cycle`   | `resetBillingCycle` used with unsupported behavior or direction        | Only allowed with `always_invoice` on upgrades                                                                                                    |
| `concurrent_modification`       | Another plan change is currently being processed for this subscription | Retry after a few seconds                                                                                                                         |

## Best Practices

<AccordionGroup>
  <Accordion title="Always preview before changing plans" icon="eye">
    Show customers exactly what they'll pay to avoid surprises and reduce support requests.

    ```javascript theme={null}
    // ✅ Good: Preview first, then confirm
    const preview = await cheqpay.subscriptions.previewPlanChange(subId, {
      newPlanId: 'plan_pro',
      prorationBehavior: 'always_invoice'
    });

    // Show the customer: "You'll be charged $X today"
    const confirmed = await showConfirmation(preview);
    if (confirmed) {
      await cheqpay.subscriptions.changePlan(subId, {
        newPlanId: 'plan_pro',
        prorationBehavior: 'always_invoice'
      });
    }

    // ❌ Bad: Change without preview
    await cheqpay.subscriptions.changePlan(subId, params);
    ```
  </Accordion>

  <Accordion title="Use idempotency keys for plan changes" icon="shield">
    Protect against duplicate charges from network retries or double-clicks.

    ```javascript theme={null}
    // ✅ Good: Include idempotency key
    const result = await fetch(`/v1/subscriptions/${subId}/plan`, {
      method: 'PATCH',
      headers: {
        'x-idempotency-key': `change-${subId}-${newPlanId}-${Date.now()}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ newPlanId, prorationBehavior: 'always_invoice' })
    });

    // Safe to retry — same key returns the same result
    ```
  </Accordion>

  <Accordion title="Use immediate proration for upgrades" icon="bolt">
    Charge customers right away when they upgrade to capture revenue immediately.

    ```javascript theme={null}
    const result = await cheqpay.subscriptions.changePlan(subId, {
      newPlanId: premiumPlanId,
      prorationBehavior: 'always_invoice'
    });
    ```
  </Accordion>

  <Accordion title="Use deferred proration for downgrades" icon="clock">
    Apply credit at next billing to avoid refunds and maintain cash flow.

    ```javascript theme={null}
    const result = await cheqpay.subscriptions.changePlan(subId, {
      newPlanId: basicPlanId,
      prorationBehavior: 'create_prorations'
    });
    // Note: downgrades are always deferred regardless of behavior
    ```
  </Accordion>

  <Accordion title="Use clear, customer-facing plan names" icon="tag">
    Plan names are shown to customers. Make them descriptive and easy to understand.

    ```javascript theme={null}
    // ✅ Good: Clear and descriptive
    await cheqpay.plans.create({
      name: 'Pro Plan - Monthly',
      description: 'Full access to all features, billed monthly'
    });

    // ❌ Bad: Internal naming
    await cheqpay.plans.create({
      name: 'TIER_2_M1',
      description: 'internal code: xyz'
    });
    ```
  </Accordion>

  <Accordion title="Use externalId for your own identifiers" icon="link">
    Link plans to your internal systems without exposing IDs to customers.

    ```javascript theme={null}
    const plan = await cheqpay.plans.create({
      name: 'Pro Plan',
      externalId: 'pro-monthly-v2',
      amount: 29999
    });
    ```
  </Accordion>

  <Accordion title="Create new plans instead of updating pricing" icon="plus">
    Amount, currency, and interval are immutable. Create new plans for price changes.

    ```javascript theme={null}
    // ✅ Good: Create new plan, migrate customers
    const newPlan = await cheqpay.plans.create({
      name: 'Pro Plan',
      externalId: 'pro-monthly-2026',
      amount: 34999
    });
    await cheqpay.subscriptions.changePlan(subId, {
      newPlanId: newPlan.id
    });
    await cheqpay.plans.archive('plan_old_pricing');
    ```
  </Accordion>

  <Accordion title="Handle plan change webhook events" icon="bell">
    Listen for subscription events to keep your system in sync.

    ```javascript theme={null}
    webhook.on('subscription.plan_changed', (event) => {
      const { subscriptionId, newPlan, changeDirection, proration } = event.data;

      // Update user access based on new plan
      await grantFeatures(subscriptionId, newPlan.id);

      // Track for analytics
      analytics.track('plan_changed', {
        direction: changeDirection,
        behavior: proration?.behavior,
        amount: proration?.chargedAmount
      });
    });
    ```
  </Accordion>

  <Accordion title="Always include security code on creation" icon="lock">
    Pass the CVV when creating subscriptions to reduce fraud and improve authorization rates.

    ```javascript theme={null}
    // ✅ Good: Include CVV
    const subscription = await cheqpay.subscriptions.create({
      customerId: 'cus_123',
      planId: 'plan_456',
      paymentMethodId: 'pm_789',
      securityCode: '123'
    });
    ```
  </Accordion>

  <Accordion title="Offer graceful cancellation" icon="heart">
    Default to end-of-period cancellation to maintain customer goodwill.

    ```javascript theme={null}
    // ✅ Good: Cancel at period end (default)
    await cheqpay.subscriptions.cancel(subId, {
      cancelImmediately: false
    });

    // Only use immediate for fraud or TOS violations
    await cheqpay.subscriptions.cancel(subId, {
      cancelImmediately: true
    });
    ```
  </Accordion>

  <Accordion title="Monitor invoice status" icon="chart-line">
    Track payment retries and handle failures gracefully.

    ```javascript theme={null}
    const { data: invoices } = await cheqpay.subscriptions.listInvoices(subId);

    const failedInvoices = invoices.filter(
      inv => inv.status === 'PAYMENT_FAILED'
    );

    if (failedInvoices.length > 0) {
      // Notify customer about failed payments
      // Offer to update payment method
    }
    ```
  </Accordion>
</AccordionGroup>

## Related Resources

* [Webhooks](/features/webhooks) - Receive real-time notifications for plan changes and other events
* [API Reference](/api-reference/introduction) - Complete endpoint documentation
* [Customers](/features/customers) - Manage customer records
* [Payment Methods](/features/payment-methods) - Manage payment methods for subscriptions
