> ## 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 Integration Guide

> This guide will help you integrate the Dodo Payments Subscription Product into your website.

## Prerequisites

To integrate the Dodo Payments API, you'll need:

* A Dodo Payments merchant account
* API credentials (API key and webhook secret key) from the dashboard

For a more detailed guide on the prerequisites, check this [section](/developer-resources/integration-guide#dashboard-setup).

## API Integration

### Checkout Sessions

Use Checkout Sessions to sell subscription products with a secure, hosted checkout. Pass your subscription product in `product_cart` and redirect customers to the returned `checkout_url`.

<Tip>
  **Mixed Checkout**: You can combine subscription products with one-time products in the same checkout session. This enables use cases like setup fees with subscriptions, hardware bundles with SaaS, and more. See the [Checkout Sessions guide](/developer-resources/checkout-session) for examples.
</Tip>

<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 main() {
      const session = await client.checkoutSessions.create({
        product_cart: [
          { product_id: 'prod_subscription_monthly', quantity: 1 }
        ],
        // Optional: configure trials for subscription products
        subscription_data: { trial_period_days: 14 },
        customer: {
          email: 'subscriber@example.com',
          name: 'Jane Doe',
        },
        return_url: 'https://example.com/success',
      });

      console.log(session.checkout_url);
    }

    main();
    ```
  </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"
    )

    session = client.checkout_sessions.create(
        product_cart=[
            {"product_id": "prod_subscription_monthly", "quantity": 1}
        ],
        subscription_data={"trial_period_days": 14},  # optional
        customer={
            "email": "subscriber@example.com",
            "name": "Jane Doe",
        },
        return_url="https://example.com/success",
    )

    print(session.checkout_url)
    ```
  </Tab>

  <Tab title="REST API">
    ```javascript theme={null}
    const response = await fetch('https://test.dodopayments.com/checkouts', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.DODO_PAYMENTS_API_KEY}`
      },
      body: JSON.stringify({
        product_cart: [
          { product_id: 'prod_subscription_monthly', quantity: 1 }
        ],
        subscription_data: { trial_period_days: 14 }, // optional
        customer: {
          email: 'subscriber@example.com',
          name: 'Jane Doe'
        },
        return_url: 'https://example.com/success'
      })
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const session = await response.json();
    console.log(session.checkout_url);
    ```
  </Tab>
</Tabs>

### API Response

The following is an example of the response:

```json theme={null}
{
  "session_id": "cks_Gi6KGJ2zFJo9rq9Ukifwa",
  "checkout_url": "https://test.checkout.dodopayments.com/session/cks_Gi6KGJ2zFJo9rq9Ukifwa"
}
```

将客户重定向到`checkout_url`。

### Webhooks

在集成订阅时，您将接收到 webhooks 以跟踪订阅生命周期。这些 webhooks 有助于您有效地管理订阅状态和支付场景。

要设置您的 webhook 端点，请按照我们的[详细集成指南](/developer-resources/integration-guide#implementing-webhooks)。

#### 订阅事件类型

以下 webhook 事件跟踪订阅状态的变化：

1. **`subscription.active`** - 订阅成功激活。
2. **`subscription.updated`** - 订阅对象已更新（任何字段更改时触发）。
3. **`subscription.on_hold`** - 由于续订失败导致订阅被暂停。
4. **`subscription.failed`** - 创建授权失败时订阅创建失败。
5. **`subscription.renewed`** - 订阅已为下一个计费周期续订。

为了可靠地管理订阅生命周期，我们建议跟踪这些订阅事件。

<Tip>
  使用 `subscription.updated` 来获取关于任何订阅更改的实时通知，使您的应用程序状态与 API 保持同步而无需轮询。
</Tip>

#### 支付场景

**成功支付流程**

您收到的 webhooks 及其时间安排取决于产品是否包含试用期。

*立即计费（0 个试用天数）：*

1. `subscription.active`：mandate 已获授权，订阅已激活。
2. `payment.succeeded`：确认首次扣款。预计在结账后的 **2–10 分钟**内收到。

*包含试用期：*

1. **试用期开始时（结账）：** payment method 获得授权后，`subscription.active` 触发。**此时不会收取 recurring charge。** 首次实际扣款会推迟到试用期结束。
2. **试用期结束时：** 系统会收取 recurring amount，您会同时收到 `payment.succeeded` **和** `subscription.renewed`。

*每次后续续订：*

* `subscription.renewed`：每个 billing cycle 在扣除续订款项时触发，**始终与** `payment.succeeded` **同时**触发。它还会携带更新后的 `next_billing_date`。

<Info>
  每当 subscription product 实际扣款时，您都会收到 `subscription.renewed` **和** `payment.succeeded`。请使用 `subscription.renewed`（而不是单独使用 `payment.succeeded`）作为延长下一周期访问权限的信号。
</Info>

**付款失败场景**

1. 订阅失败

* `subscription.failed` - 由于创建 mandate 失败，订阅创建失败。
* `payment.failed` - 表示付款失败。

2. 订阅暂停

* `subscription.on_hold` - 由于续订付款失败或计划变更扣款失败，订阅被暂停。
* 订阅暂停后，在 payment method 更新之前不会自动续订。

<Info>**最佳实践**：为简化实现，我们建议主要跟踪 subscription events，以管理订阅生命周期。</Info>

<Tip>
  如需完整了解如何读取 `error_code`/`error_message`、决定何时重试以及向客户展示失败信息，请参阅[处理付款失败](/developer-resources/handle-payment-failures)。
</Tip>

#### `subscription.failed` 与 `subscription.on_hold`

这两个事件很容易混淆，但处理方式完全不同：

| 事件                     | 触发时机                        | 状态        | 是否可恢复？      | 操作                                                                        |
| ---------------------- | --------------------------- | --------- | ----------- | ------------------------------------------------------------------------- |
| `subscription.failed`  | 订阅**创建**时无法创建**初始** mandate | `failed`  | **否（终止状态）** | 不要授予访问权限。要求客户使用其他 payment method 创建一个**新**订阅。                             |
| `subscription.on_hold` | 已激活订阅的**续订**付款（或计划变更扣款）失败   | `on_hold` | **是**       | 通过更新 payment method 进行恢复；请参阅下方的[处理暂停的订阅](#handling-subscription-on-hold)。 |

<Warning>
  `subscription.failed` 是终止状态。订阅无法重新激活。客户必须创建新订阅。该事件触发时，切勿授予 entitlements。
</Warning>

### 处理暂停的订阅

当订阅进入 `on_hold` 状态时，您需要更新 payment method 才能重新激活订阅。本节介绍订阅何时会暂停以及如何处理。

#### 订阅暂停的情况

在以下情况下，订阅会被暂停：

* **续订付款失败**：由于余额不足、卡片过期或银行拒绝，自动续订扣款失败
* **计划变更扣款失败**：升级或降级计划期间的即时扣款失败
* **payment method 授权失败**：payment method 无法获得 recurring charges 的授权

<Warning>
  处于 `on_hold` 状态的订阅不会自动续订。您必须更新 payment method 才能重新激活订阅。
</Warning>

#### 从暂停状态重新激活订阅

要将处于 `on_hold` 状态的订阅重新激活，请使用 Update Payment Method API。该 API 会自动：

1. 为剩余应付款创建扣款
2. 为该扣款生成 invoice
3. 使用新的 payment method 处理付款
4. 付款成功后，将订阅重新激活为 `active` 状态

<Steps>
  <Step title="Handle subscription.on_hold webhook">
    收到 `subscription.on_hold` webhook 后，请更新应用状态并通知客户：

    ```javascript theme={null}
    // Webhook handler
    app.post('/webhooks/dodo', async (req, res) => {
      const event = req.body;
      
      if (event.type === 'subscription.on_hold') {
        const subscription = event.data;
        
        // Update subscription status in your database
        await updateSubscriptionStatus(subscription.subscription_id, 'on_hold');
        
        // Notify customer to update payment method
        await sendEmailToCustomer(subscription.customer_id, {
          subject: 'Payment Required - Subscription On Hold',
          message: 'Your subscription is on hold. Please update your payment method to continue service.'
        });
      }
      
      res.json({ received: true });
    });
    ```
  </Step>

  <Step title="Update payment method">
    客户准备好更新 payment method 后，请调用 Update Payment Method API：

    <CodeGroup>
      ```javascript Node.js theme={null}
      // Update with new payment method
      const response = await client.subscriptions.updatePaymentMethod(subscriptionId, {
        type: 'new',
        return_url: 'https://example.com/return'
      });

      // For on_hold subscriptions, a charge is automatically created
      if (response.payment_id) {
        console.log('Charge created for remaining dues:', response.payment_id);
        // Redirect customer to response.payment_link to complete payment
      }
      ```

      ```python Python theme={null}
      # Update with new payment method
      response = client.subscriptions.update_payment_method(
          subscription_id=subscription_id,
          type="new",
          return_url="https://example.com/return"
      )

      # For on_hold subscriptions, a charge is automatically created
      if response.payment_id:
          print("Charge created for remaining dues:", response.payment_id)
          # Redirect customer to response.payment_link to complete payment
      ```
    </CodeGroup>

    <Info>
      如果客户已保存 payment methods，您也可以使用现有的 payment method ID：

      ```javascript theme={null}
      await client.subscriptions.updatePaymentMethod(subscriptionId, {
        type: 'existing',
        payment_method_id: 'pm_abc123'
      });
      ```
    </Info>
  </Step>

  <Step title="Monitor webhook events">
    更新 payment method 后，请监控以下 webhook events：

    1. **`payment.succeeded`** - 剩余应付款的扣款成功
    2. **`subscription.active`** - 订阅已重新激活

    ```javascript theme={null}
    if (event.type === 'payment.succeeded') {
      const payment = event.data;
      
      // Check if this payment is for an on_hold subscription
      if (payment.subscription_id) {
        // Wait for subscription.active webhook to confirm reactivation
      }
    }

    if (event.type === 'subscription.active') {
      const subscription = event.data;
      
      // Update subscription status in your database
      await updateSubscriptionStatus(subscription.subscription_id, 'active');
      
      // Restore customer access
      await restoreCustomerAccess(subscription.customer_id);
      
      // Notify customer of successful reactivation
      await sendEmailToCustomer(subscription.customer_id, {
        subject: 'Subscription Reactivated',
        message: 'Your subscription has been reactivated successfully.'
      });
    }
    ```
  </Step>
</Steps>

### Subscription event payload 示例

***

| 属性            | 类型     | 必填 | 描述                                           |
| ------------- | ------ | -- | -------------------------------------------- |
| `business_id` | string | 是  | business 的唯一标识符                              |
| `timestamp`   | string | 是  | 事件发生的时间戳（不一定与事件交付时间相同）                       |
| `type`        | string | 是  | 事件类型。请参阅 [Event Types](#event-types)         |
| `data`        | object | 是  | 主要数据 payload。请参阅 [Data Object](#data-object) |

## 更改订阅计划

您可以使用 change plan API endpoint 升级或降级订阅计划。这允许您修改订阅的 product、quantity，并处理 proration。

<Card title="Change Plan API Reference" icon="arrows-rotate" href="/api-reference/subscriptions/change-plan">
  有关更改订阅计划的详细信息，请参阅我们的 Change Plan API 文档。
</Card>

### Proration 选项

更改订阅计划时，您可以选择以下两种方式处理即时扣款：

#### 1. `prorated_immediately`

* 根据当前 billing cycle 的剩余时间计算按比例分摊的金额
* 仅向客户收取新旧计划之间的差额
* 在试用期内，这会立即将用户切换到新计划，并立即向客户收费

#### 2. `full_immediately`

* 向客户收取新计划的完整 subscription amount
* 忽略之前计划的剩余时间或 credits
* 适用于您希望重置 billing cycle，或无论 proration 如何都收取完整金额的情况

#### 3. `difference_immediately`

* 升级时，立即向客户收取两个计划金额之间的差额。
* 例如，如果当前计划为 30 Dollars，客户升级到 80 Dollars，则会立即收取 \$50。
* 降级时，当前计划的未使用金额会添加为 internal credit，并自动用于抵扣未来的订阅续订费用。
* 例如，如果当前计划为 50 Dollars，客户切换到 20 Dollars 的计划，则剩余的 \$30 会记为 credit，并用于下一个 billing cycle。

#### 4. `do_not_bill`

* 立即应用计划变更，但在变更时**不会**收取任何费用。
* 更新后的计划（以及 quantity/add-ons）会在**下一次计划续订**时计费，并且会**保留原 billing date**。

<Warning>
  **所有三种“立即扣款”模式都会重置 billing cycle。** `prorated_immediately`、`difference_immediately` 和 `full_immediately` 会将订阅的 `next_billing_date` 移至变更日期。只有 `do_not_bill` 会保留原续订日期，但不会立即扣款。
</Warning>

### 行为

* 调用此 API 时，Dodo Payments 会根据您选择的 proration 选项立即发起扣款
* 如果计划变更为降级，并且您使用 `prorated_immediately`，系统会自动计算 credits 并将其添加到订阅的 credit balance 中。这些 credits 专属于该订阅，只会用于抵扣同一订阅未来的 recurring payments
* `full_immediately` 选项会跳过 credit calculations，并收取新计划的完整金额

<Tip>
  **请谨慎选择 proration 选项**：如果需要根据未使用时间进行公平计费，请使用 `prorated_immediately`；如果无论当前 billing cycle 如何都要收取新计划的完整金额，请使用 `full_immediately`。
</Tip>

### 扣款处理

* 计划变更时发起的即时扣款通常会在 2 分钟内完成处理
* 如果即时扣款因任何原因失败，订阅会自动进入暂停状态，直到问题得到解决

## 按需订阅

<Info>
  按需订阅允许您灵活地向客户收费，而不局限于固定时间表。所有账户均可使用此功能。
</Info>

**创建按需订阅：**

要创建按需订阅，请使用 [POST /subscriptions](/api-reference/subscriptions/post-subscriptions) API endpoint，并在 request body 中包含 `on_demand` 字段。这样可以在不立即扣款的情况下授权 payment method，或设置自定义初始价格。

**向按需订阅收费：**

对于后续扣款，请使用 [POST /subscriptions/{subscription_id}/charge](/api-reference/subscriptions/create-charge) endpoint，并指定要针对该交易向客户收取的金额。

<Note>
  如需完整的分步指南（包括 request/response 示例、安全的重试策略和 webhook 处理），请参阅 <a href="/developer-resources/ondemand-subscriptions">按需订阅指南</a>。
</Note>

## 关于订阅计费的关键事项

<Warning>
  **将订阅周期设置得长于付款频率。** 如果 subscription period 等于 payment frequency（例如 period = 1 month、frequency = 1 month），订阅只对**一个周期**有效，随后会进入 `expired`，而不是续订。对于持续的月度计划，请设置较长的 subscription period（例如 20 年），并使用月度 payment frequency。
</Warning>

<Warning>
  **货币会在首次成功扣款时锁定。** 创建 checkout 时，请始终明确传递 `billing_currency` **和** `billing_address.country`。如果省略，系统会根据客户的 IP 检测货币（Adaptive Currency）；订阅首次扣款后，货币将在其生命周期内固定不变。客户之后旅行也无法切换货币。
</Warning>

<Info>
  **试用期执行的是 \$0 授权，而不是扣款。** 当订阅包含试用期时，试用期开始会创建一次 **\$0 mandate authorization** 以保存卡片；首次实际扣款会在试用期结束时发生。在 payments list 中，处于试用期的订阅会显示一笔且仅一笔包含 `amount: 0` 的 payment。
</Info>

<Info>
  **订阅生命周期：** `on_hold` = 续订失败（可恢复：提示客户更新 payment method；dunning retries 适用）。`expired` = 期限结束且未续订，**无法重新激活**。客户必须重新订阅。`cancelled` = 由客户或 merchant 结束。大多数续订失败是**发卡方拒绝**（余额不足、卡片被拒绝），而不是 Dodo 错误。
</Info>

<Warning>
  **印度卡片使用 RBI e-mandate。** Off-session charges（续订和计划变更扣款）最多可能需要 **约 48 小时**才能结算，并且 **超过 ₹15,000** 的 recurring auto-debits 需要客户重新进行身份验证（因此，超过该限额的升级无法使用现有 mandate）。当一笔扣款仍处于 `processing` 状态时，同一订阅的第二笔扣款会失败，并显示 *"Cannot create new charge as previous payment is not successful yet."* 非印度卡片通常会即时确认。
</Warning>

<Tip>
  **Subscription charges 的最低金额为 \$1**（或等值货币）。金额为 `$0.01–$0.99` 的扣款会被 `product_price: value out of range` 拒绝；只有通过按需 `mandate_only` setup 才允许使用 `$0`。
</Tip>

## 相关 API 参考

<CardGroup cols={2}>
  <Card title="Create Subscription" icon="code" href="/api-reference/subscriptions/post-subscriptions">
    用于创建 subscription products 和管理订阅生命周期的 API 参考
  </Card>

  <Card title="Change Subscription Plan" icon="arrows-rotate" href="/api-reference/subscriptions/change-plan">
    用于通过 proration 选项升级、降级或更改订阅计划的 API 参考
  </Card>

  <Card title="Update Payment Method" icon="credit-card" href="/api-reference/subscriptions/update-payment-method">
    用于更新 payment methods 和重新激活暂停订阅的 API 参考
  </Card>

  <Card title="Patch Subscription" icon="pen" href="/api-reference/subscriptions/patch-subscriptions">
    用于更新订阅详细信息和配置的 API 参考
  </Card>
</CardGroup>
