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

# 手动支付重试

> 无需等待下一次自动重试，直接从控制面板或 API 按需重试失败的订阅续费支付。

<Info>
  Manual Retry 会在您发起请求时，从支付详情页或通过 API 重新尝试失败的订阅**续费**支付。它会对订阅中已保存的支付方式发起扣款，并独立于自动 [Payment Retries](/features/recovery/payment-retries) 计划运行。
</Info>

## 什么是手动重试？

续费支付失败后，订阅会变为 `on_hold`。如果您启用了 [Payment Retries](/features/recovery/payment-retries)，系统会按照退避计划重新尝试扣款。有时您知道现在付款一定会成功：客户已确认他们为账户充值，或者您的支持团队正在与客户通话。Manual Retry 会立即发起一次尝试，而不是等待数小时或数天后的下一次计划尝试。

* **仅限续费支付**：Manual Retry 适用于订阅处于 `on_hold` 状态时的订阅续费发票。首次支付、一次性支付、套餐变更费用和按需费用不符合条件。
* **无需客户操作**：扣款会发送至订阅中已保存的支付方式。
* **独立于自动重试**：手动重试不会消耗自动计划中的一次尝试，不会移动下一次计划重试的时间，即使关闭了 Payment Retries 也可以使用。
* **重试的是发票，而不是支付**：失败支付只是入口。Dodo Payments 会找到其对应的未结清续费发票，并对该欠款进行扣款，因此从发票上的哪个失败支付发起重试并不重要。

## 从控制面板重试

只有 Owner 或 Editor 角色的用户才能发送手动重试。

<Steps>
  <Step title="Open the failed payment">
    前往 **Transactions → Payments**，点击失败的续费支付，打开其 **Transaction details** 页面。
  </Step>

  <Step title="Click Retry Payment Manually">
    点击右上角的 **Retry Payment Manually**。该按钮仅在支付[符合条件](#eligibility)时可用。
  </Step>

  <Step title="Check the result">
    Dodo Payments 会为本次尝试创建新的支付，该支付会显示在 **Activity Log** 中。如果扣款成功，订阅会恢复为 `active`，下一次计费日期会移至成功重试后一个计费周期。如果支付处理商尚未结算扣款，支付会显示为进行中，直到 `payment.succeeded` 或 `payment.failed` webhook 报告结果。
  </Step>
</Steps>

<Frame caption="Retry Payment Manually on the transaction details page of a failed renewal">
  <img src="https://mintcdn.com/dodopayments/0duTS18kYi2NwQ3m/images/recovery/manual-retry-transaction-details.png?fit=max&auto=format&n=0duTS18kYi2NwQ3m&q=85&s=5537fa5eff17cbe91a53599f887a26e7" alt="显示失败支付的错误代码和消息、Activity Log 以及 Retry Payment Manually 按钮的 Transaction details 页面" style={{ maxHeight: '500px', width: 'auto' }} width="1285" height="698" data-path="images/recovery/manual-retry-transaction-details.png" />
</Frame>

## 符合条件

只有此表中的每项检查均通过时，Dodo Payments 才会发送手动重试。**Reason code** 列显示 API 返回的内容：在 `GET /payments/{payment_id}/retry` 上为 `reason`，在 `POST /payments/{payment_id}/retry` 上则作为错误 `code` 返回。

| 检查项 | 要求 | Reason code |
| - | - | - |
| 支付类型 | 订阅的**续费**支付，且其发票仍处于未结清状态。没有发票的支付、首次支付、一次性支付、套餐变更费用和按需费用无法重试。 | `PAYMENT_NOT_RETRYABLE` |
| 订阅状态 | `on_hold` | `SUBSCRIPTION_INACTIVE` |
| 计划取消 | 订阅未计划在下一次计费日期取消。 | `CHARGE_NOT_ALLOWED_FOR_SCHEDULED_CANCELLATION` |
| 已保存的支付方式 | 订阅有可用于扣款的已保存支付方式。 | `SUBSCRIPTION_HAS_NO_PAYMENT_METHOD` |
| 最近一次失败 | 最近一次失败是**软拒付**。硬拒付或没有已分类错误代码的失败无法重试。 | `MANUAL_RETRY_HARD_DECLINE` |
| 没有进行中的支付 | 发票上没有支付处于 `processing` 状态或仍没有记录状态。此类支付表示刚刚发送的手动或自动尝试，尚未返回结果。请先等待其结果。 | `MANUAL_RETRY_IN_FLIGHT` |
| 最新支付失败 | 发票上最近一次支付的状态为 `failed`。任何其他状态，例如 `requires_customer_action`、`requires_payment_method` 或 `cancelled`，都会阻止重试，即使当前没有进行中的支付。 | `PREVIOUS_PAYMENT_PENDING` |
| 尚未支付成功 | 发票上没有任何支付成功。 | `MANUAL_RETRY_ALREADY_PAID` |
| 重试上限 | 该发票发送的手动重试少于 3 次，并且冷却时间已结束。请参阅 [Retry Limits](#retry-limits)。 | `MANUAL_RETRY_LIMIT_REACHED` |
| 客户 | 客户不在您的[阻止列表](/features/customer-blocklist)中。 | `PAYMENT_NOT_RETRYABLE` |
| 支付连接器 | 对于 [BYOP](/features/byop) 订阅，连接器已启用。 | `BYOP_CONNECTOR_DISABLED` |
| 实时模式 | 在实时模式下，您的业务已启用实时支付。 | `MERCHANT_NOT_LIVE` |

<Note>
  Manual Retry 在一个方面比自动重试更严格：它要求订阅处于 `on_hold` 状态。处于宽限期的 `past_due` 订阅无法通过此项检查。对于其他非 active 状态，自动重试仍会继续运行。请参阅 [Subscription Status Transitions](/features/recovery/payment-retries#subscription-status-transitions)。
</Note>

<Warning>
  针对同一张卡重试硬拒付不会成功，反复被拒还会降低您的授权率。当原因是 `MANUAL_RETRY_HARD_DECLINE` 时，请改为要求客户更新其支付方式。[Subscription Dunning](/features/recovery/subscription-dunning) 会自动执行此操作。
</Warning>

## 重试限制

每张续费发票允许进行 **3** 次手动重试，每次重试之间有冷却时间：

| 手动重试 | 可用时间 |
| - | - |
| 1 | 支付符合条件后立即可用 |
| 2 | 第一次重试后 1 小时 |
| 3 | 第二次重试后 3 小时 |

这些限制同时适用于测试模式和实时模式。当达到上限而拒绝重试时，API 会返回 `MANUAL_RETRY_LIMIT_REACHED`（HTTP `429`），错误正文只包含 `code` 和 `message`。要了解下一次重试何时开放，请[检查重试状态](#check-whether-a-payment-can-be-retried)，并读取 `retry_available_at`。当三次重试全部用完后，该字段为 `null`。

自动重试不会计入此上限，手动重试也不会计入自动计划的 8 次尝试。

## 手动重试与自动重试

两种重试路径的区别如下：

| | Manual Retry | Payment Retries |
| - | - | - |
| **触发方** | 您，从控制面板或 API 发起 | Dodo Payments，按退避计划发起 |
| **时间** | 立即 | 失败后 12 小时，然后逐步延后 |
| **尝试次数** | 每张发票 3 次，冷却时间依次为 1 小时和 3 小时 | 每张发票最多 8 次，在您的恢复窗口内进行 |
| **需要启用 Payment Retries** | 否 | 是 |
| **对另一种重试的影响** | 无。手动失败不会安排或移动自动尝试。 | 无。无论是否发送手动重试，自动链都会继续。 |
| **分析** | 计入 **Analytics → Recovery** 下的 **Payment Retries** 指标 | 计入相同指标 |

## 通过 API 重试

先检查是否符合条件，然后发送重试。两个 endpoint 都需要传入失败支付的 ID。

### 检查支付是否可以重试

对于不符合条件的支付，`GET /payments/{payment_id}/retry` 不会返回错误。它会返回 `can_retry: false`，并使用 `reason` 代码，因此您自己的控制面板或支持工具可以显示与 Dodo Payments 控制面板相同的状态。此操作需要 **Viewer** 角色。

<CodeGroup>
  ```typescript Node.js theme={null}
  import DodoPayments from 'dodopayments';

  const client = new DodoPayments({
    bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  });

  const state = await client.payments.retrieveRetryState('pay_0NmDtkE0iRvmeTcT6t0ol');

  if (state.can_retry) {
    console.log(`Retry available. ${state.sends_used}/${state.sends_allowed} used.`);
  } else {
    console.log(`Cannot retry: ${state.reason}. Next window: ${state.retry_available_at}`);
  }
  ```

  ```python Python theme={null}
  import os
  from dodopayments import DodoPayments

  client = DodoPayments(bearer_token=os.environ["DODO_PAYMENTS_API_KEY"])

  state = client.payments.retrieve_retry_state("pay_0NmDtkE0iRvmeTcT6t0ol")

  if state.can_retry:
      print(f"Retry available. {state.sends_used}/{state.sends_allowed} used.")
  else:
      print(f"Cannot retry: {state.reason}. Next window: {state.retry_available_at}")
  ```

  ```bash cURL theme={null}
  curl https://live.dodopayments.com/payments/pay_0NmDtkE0iRvmeTcT6t0ol/retry \
    -H "Authorization: Bearer $DODO_PAYMENTS_API_KEY"
  ```
</CodeGroup>

```json Response theme={null}
{
  "can_retry": false,
  "reason": "MANUAL_RETRY_LIMIT_REACHED",
  "sends_used": 1,
  "sends_allowed": 3,
  "retry_available_at": "2026-08-26T16:51:00Z"
}
```

| 字段 | 描述 |
| - | - |
| `can_retry` | 如果此时发送重试，则为 `true`。 |
| `reason` | 重试将失败时返回的代码。当 `can_retry` 为 `true` 时，该字段为 `null`。 |
| `sends_used` | 已在此发票上发送的手动重试次数。 |
| `sends_allowed` | 始终为 `3`。 |
| `retry_available_at` | 下一次手动重试开放的时间。当没有剩余重试次数，或拒绝原因与冷却时间无关时，该字段为 `null`。 |

### 发送手动重试

`POST /payments/{payment_id}/retry` 会创建新的支付并对已保存的支付方式扣款。此操作需要 **Editor** 角色。SDK 示例会复用上一个示例中的 `client`。

<CodeGroup>
  ```typescript Node.js theme={null}
  // `client` is the DodoPayments instance from the previous example.
  const retry = await client.payments.retry('pay_0NmDtkE0iRvmeTcT6t0ol');

  console.log(retry.payment_id, retry.status);
  ```

  ```python Python theme={null}
  # `client` is the DodoPayments instance from the previous example.
  retry = client.payments.retry("pay_0NmDtkE0iRvmeTcT6t0ol")

  print(retry.payment_id, retry.status)
  ```

  ```bash cURL theme={null}
  curl -X POST https://live.dodopayments.com/payments/pay_0NmDtkE0iRvmeTcT6t0ol/retry \
    -H "Authorization: Bearer $DODO_PAYMENTS_API_KEY"
  ```
</CodeGroup>

```json Response theme={null}
{
  "payment_id": "pay_2IjeQm4hqU6RA4Z4kwDee",
  "invoice_id": "inv_9Kp2mQ7vRt4LxYw3",
  "status": "processing",
  "retry_attempt": 1,
  "is_manual_retry": true,
  "sends_used": 1,
  "sends_allowed": 3,
  "retry_available_at": "2026-08-26T16:51:00Z"
}
```

| 字段 | 描述 |
| - | - |
| `payment_id` | 为本次尝试创建的新支付。 |
| `invoice_id` | 被扣款的续费发票。 |
| `status` | 扣款结果。`processing` 表示支付处理商尚未结算。`null` 表示响应返回前尚未记录结果。在这两种情况下，支付 webhook 都会报告最终结果。 |
| `retry_attempt` | 此次尝试在该发票手动重试中的位置，从 `1` 开始编号。 |
| `is_manual_retry` | 此 endpoint 始终为 `true`。 |
| `sends_used`、`sends_allowed`、`retry_available_at` | 此次发送后的重试限制状态。`retry_available_at` 仅表示冷却计时器。即使此次扣款成功，该字段也会被设置；此时发票已支付成功，不会再开放后续重试。 |

### 错误响应

不允许重试时，`POST` 会返回以下 HTTP 状态之一：

| HTTP 状态 | Codes | 处理方式 |
| - | - | - |
| `404` | `NOT_FOUND` | 此支付不属于您的业务。 |
| `409` | `MANUAL_RETRY_IN_FLIGHT`、`PREVIOUS_PAYMENT_PENDING`、`CHARGE_NOT_ALLOWED_FOR_SCHEDULED_CANCELLATION` | 此阻止是临时的，或者需要先更改其他条件。等待进行中的支付或待处理支付达到最终状态，或取消计划中的取消操作。 |
| `422` | `PAYMENT_NOT_RETRYABLE`、`SUBSCRIPTION_INACTIVE`、`SUBSCRIPTION_HAS_NO_PAYMENT_METHOD`、`MANUAL_RETRY_HARD_DECLINE`、`MANUAL_RETRY_ALREADY_PAID`、`BYOP_CONNECTOR_DISABLED`、`MERCHANT_NOT_LIVE` | 此支付无法重试。不要重复调用。 |
| `429` | `MANUAL_RETRY_LIMIT_REACHED` | [检查重试状态](#check-whether-a-payment-can-be-retried)，并等待 `retry_available_at`，或者在三次重试全部用完后停止。 |

[Error Codes](/api-reference/error-codes) 参考文档介绍了每个代码。

## Webhooks

手动重试会创建普通支付，因此会触发与任何续费尝试相同的 webhook：

| Event | 触发时机 |
| - | - |
| `payment.succeeded` | 重试扣款成功。随后会触发 `subscription.active`，订阅也会恢复。 |
| `payment.failed` | 重试被拒付。订阅保持为 `on_hold`，手动失败不会安排自动重试。 |
| `payment.processing` | 支付处理商已接受扣款，但尚未结算。 |

在这些事件的支付对象中，`retry_attempt` 为 `1` 或更高，并且已设置 `subscription_id`，这与自动重试相同。支付对象没有用于标记手动尝试的字段，因此如果需要区分手动尝试和计划尝试，请保留重试响应中的 `payment_id`。

<Card title="Payment Webhook Payloads" icon="webhook" href="/developer-resources/webhooks/intents/payment">
  支付事件的完整 payload schema。
</Card>

## 相关内容

<CardGroup cols={2}>
  <Card title="Subscription Payment Retries" icon="arrow-rotate-right" href="/features/recovery/payment-retries">
    与手动重试并行运行的自动退避计划。
  </Card>

  <Card title="Subscription Dunning" icon="repeat" href="/features/recovery/subscription-dunning">
    硬拒付后发送电子邮件，要求客户更新其支付方式。
  </Card>

  <Card title="Handle Payment Failures" icon="screwdriver-wrench" href="/developer-resources/handle-payment-failures">
    读取拒付代码，并判断何时值得重试。
  </Card>

  <Card title="Error Codes" icon="triangle-exclamation" href="/api-reference/error-codes">
    每个 `MANUAL_RETRY_*` 代码、其触发条件及消息。
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.