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

1. `subscription.active` - 表示订阅激活
2. `payment.succeeded` - 确认初始付款：
   * 对于即时计费（0 试用天数）：预计在 2-10 分钟内
   * 对于试用期：试用结束时
3. `subscription.renewed` - 表示付款扣除并续订下一个周期。（基本上，每当订阅产品付款被扣除时，您将获得 `subscription.renewed` webhook 以及 `payment.succeeded`）

**支付失败场景**

1. 订阅失败

* `subscription.failed` - 由于创建授权失败导致订阅创建失败。
* `payment.failed` - 表示付款失败。

2. 订阅暂停

* `subscription.on_hold` - 由于续订支付失败或计划更改收费失败导致订阅被暂停。
* 当订阅被暂停时，它将不会自动续订，直到更新支付方式。

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

<Tip>
  要完全了解如何读取 `error_code`/`error_message`，决定何时重试，并向客户显示失败，参见[处理支付失败](/developer-resources/handle-payment-failures)。
</Tip>

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

这两个事件容易混淆，但它们需要非常不同的处理：

| 事件                     | 触发时间                       | 状态        | 可恢复？          | 如何处理                                                     |
| ---------------------- | -------------------------- | --------- | ------------- | -------------------------------------------------------- |
| `subscription.failed`  | **初始**授权无法在订阅**创建**时创建     | `failed`  | **不可恢复 - 终端** | 不要授予访问权限。要求客户使用不同的支付方式开始**新**的订阅。                        |
| `subscription.on_hold` | 已激活订阅的**续订**付款（或计划更改收费）失败时 | `on_hold` | **是**         | 通过更新支付方式恢复；参见下文[处理订阅暂停](#handling-subscription-on-hold)。 |

<Warning>
  `subscription.failed` 是终端——订阅不能重新激活。客户必须创建新的订阅。此事件触发时绝不授予权限。
</Warning>

### 处理订阅暂停

当订阅进入 `on_hold` 状态时，您需要更新支付方式以重新激活。此部分解释订阅何时会暂停以及如何处理。

#### 订阅何时暂停

当发生以下情况时，订阅被暂停：

* **续订付款失败**：由于资金不足、卡片过期或银行拒绝导致的自动续订收费失败
* **计划更改费用失败**：在计划升级/降级期间的即时收费失败
* **支付方式授权失败**：支付方式无法授权用于定期收费

<Warning>
  在 `on_hold` 状态下的订阅将不会自动续订。您必须更新支付方式以重新激活订阅。
</Warning>

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

要从 `on_hold` 状态重新激活订阅，请使用更新支付方式 API。这会自动：

1. 为剩余款项创建收费
2. 为费用生成发票
3. 使用新支付方式处理付款
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">
    当客户准备好更新他们的支付方式时，请调用更新支付方式 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>
      如果客户已保存支付方式，您还可以使用现有支付方式 ID：

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

  <Step title="Monitor webhook events">
    更新支付方式后，监控这些 webhook 事件：

    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>

### 示例订阅事件负载

***

| 属性            | 类型     | 必需  | 描述                            |
| ------------- | ------ | --- | ----------------------------- |
| `business_id` | string | Yes | 业务的唯一标识符                      |
| `timestamp`   | string | Yes | 事件发生的时间戳（不一定与交付时间相同）          |
| `type`        | string | Yes | 事件类型。参见[事件类型](#event-types)   |
| `data`        | object | Yes | 主要数据负载。参见[数据对象](#data-object) |

## 更改订阅计划

您可以使用变更计划 API 端点升级或降级订阅计划。这允许您修改订阅的产品、数量，并处理分摊。

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

### 分摊选项

更改订阅计划时，您有两种方式处理即时收费：

#### 1. `prorated_immediately`

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

#### 2. `full_immediately`

* 向客户收取新计划的全部订阅金额
* 忽略以前计划的任何剩余时间或积分
* 在您想要重置计费周期或无论分摊如何都要收取全部金额时使用

#### 3. `difference_immediately`

* 升级时，客户会立即被收取两个计划金额之间的差额。
* 例如，如果当前计划为 30 美元，客户升级到 80 美元，则立即收取 50 美元。
* 降级时，当前计划的未使用金额将被添加为内部积分，并自动应用于未来的订阅续订。
* 例如，如果当前计划为 50 美元，客户转换到 20 美元计划，剩余的 30 美元将被记入并用于下一个计费周期。

### 行为

* 当您调用此 API 时，Dodo Payments 会立即根据您的分摊选项发起收费
* 如果计划更改是降级并且您使用 `prorated_immediately`，将自动计算积分并添加到订阅的积分余额中。这些积分是特定于该订阅的，只会用于抵消同一订阅的未来定期付款
* `full_immediately` 选项绕过积分计算并收取完整的新计划金额

<Tip>
  **谨慎选择分摊选项**：使用 `prorated_immediately` 进行公平计费，以考虑未使用时间，或在您想要无视当前计费周期收取完整新计划金额时使用 `full_immediately`。
</Tip>

### 收费处理

* 计划更改时发起的即时收费通常在不到 2 分钟内完成处理
* 如果此即时收费因任何原因失败，则订阅将自动暂停，直到问题解决

## 按需订阅

<Info>
  按需订阅允许您灵活地向客户收费，而不仅仅是根据固定计划。此功能适用于所有账户。
</Info>

**创建按需订阅：**

要创建按需订阅，请使用 [POST /subscriptions](/api-reference/subscriptions/post-subscriptions) API 端点，并在请求正文中包含 `on_demand` 字段。这允许您授权支付方式而不进行即时收费，或设置自定义初始价格。

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

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

<Note>
  有关完整的分步指南，包括请求/响应示例、安全重试策略和 webhook 处理，请参见<a href="/developer-resources/ondemand-subscriptions">按需订阅指南</a>。
</Note>

## 相关 API 参考

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

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

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

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