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

# Thử lại thanh toán thủ công

> Thử lại khoản thanh toán gia hạn subscription bị lỗi theo yêu cầu từ dashboard hoặc API thay vì chờ lần thử lại tự động tiếp theo.

<Info>
  Manual Retry thử lại khoản thanh toán **gia hạn** subscription bị lỗi khi bạn yêu cầu, từ trang chi tiết khoản thanh toán hoặc thông qua API. Tính năng này tính phí phương thức thanh toán đã lưu trên subscription và hoạt động độc lập với lịch [Payment Retries](/features/recovery/payment-retries) tự động.
</Info>

## Thử lại thủ công là gì?

Khi khoản thanh toán gia hạn bị lỗi, subscription chuyển sang `on_hold`. Nếu bạn đã bật [Payment Retries](/features/recovery/payment-retries), tính năng này sẽ thử lại khoản phí theo lịch back-off. Đôi khi bạn biết khoản thanh toán sẽ thành công ngay lúc này: customer đã xác nhận họ đã nạp tiền vào tài khoản, hoặc đội ngũ hỗ trợ của bạn đang trao đổi với họ. Manual Retry gửi một lần thử ngay lập tức thay vì chờ hàng giờ hoặc hàng ngày cho lần thử theo lịch tiếp theo.

* **Chỉ áp dụng cho khoản thanh toán gia hạn**: Manual Retry áp dụng cho các hóa đơn gia hạn subscription trong khi subscription ở trạng thái `on_hold`. Khoản thanh toán đầu tiên, khoản thanh toán một lần, khoản phí thay đổi plan và khoản phí theo yêu cầu không đủ điều kiện.
* **Không cần customer thực hiện thao tác**: Khoản phí được tính vào phương thức thanh toán đã lưu trên subscription.
* **Độc lập với các lần thử lại tự động**: Một lần thử lại thủ công không sử dụng một lần thử trong lịch tự động, không thay đổi lần thử theo lịch tiếp theo và vẫn hoạt động khi Payment Retries bị tắt.
* **Thử lại hóa đơn, không phải khoản thanh toán**: Khoản thanh toán bị lỗi chỉ là điểm bắt đầu. Dodo Payments tìm hóa đơn gia hạn đang mở đứng sau đó và thu khoản nợ tương ứng, vì vậy không quan trọng bạn thử lại từ khoản thanh toán bị lỗi nào trên hóa đơn.

## Thử lại từ Dashboard

Chỉ người dùng có vai trò Owner hoặc Editor mới có thể gửi một lần thử lại thủ công.

<Steps>
  <Step title="Open the failed payment">
    Đi tới **Transactions → Payments** và nhấp vào khoản thanh toán gia hạn bị lỗi để mở trang **Transaction details**.
  </Step>

  <Step title="Click Retry Payment Manually">
    Nhấp vào **Retry Payment Manually** ở góc trên bên phải. Nút này chỉ khả dụng khi khoản thanh toán [đủ điều kiện](#eligibility).
  </Step>

  <Step title="Check the result">
    Dodo Payments tạo một khoản thanh toán mới cho lần thử và khoản thanh toán này xuất hiện trong **Activity Log**. Nếu khoản phí thành công, subscription trở về trạng thái `active` và ngày lập hóa đơn tiếp theo được chuyển sang một billing period sau lần thử lại thành công. Nếu payment processor chưa hoàn tất việc quyết toán khoản phí, khoản thanh toán sẽ hiển thị là đang xử lý cho đến khi webhook `payment.succeeded` hoặc `payment.failed` báo cáo kết quả.
  </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="Trang chi tiết giao dịch của một khoản thanh toán bị lỗi, hiển thị mã lỗi và thông báo lỗi, Activity Log cùng nút Retry Payment Manually" style={{ maxHeight: '500px', width: 'auto' }} width="1285" height="698" data-path="images/recovery/manual-retry-transaction-details.png" />
</Frame>

## Điều kiện đủ

Dodo Payments chỉ gửi một lần thử lại thủ công khi mọi điều kiện trong bảng này đều đạt. Cột **Reason code** là giá trị API trả về: trong `reason` trên `GET /payments/{payment_id}/retry` và dưới dạng lỗi `code` trên `POST /payments/{payment_id}/retry`.

| Kiểm tra | Yêu cầu | Reason code |
| - | - | - |
| Loại khoản thanh toán | Khoản thanh toán **gia hạn** subscription có hóa đơn vẫn đang mở. Không thể thử lại các khoản thanh toán không có hóa đơn, khoản thanh toán đầu tiên, khoản thanh toán một lần, khoản phí thay đổi plan và khoản phí theo yêu cầu. | `PAYMENT_NOT_RETRYABLE` |
| Trạng thái subscription | `on_hold` | `SUBSCRIPTION_INACTIVE` |
| Hủy theo lịch | Subscription không được lên lịch hủy vào ngày lập hóa đơn tiếp theo. | `CHARGE_NOT_ALLOWED_FOR_SCHEDULED_CANCELLATION` |
| Phương thức thanh toán đã lưu | Subscription có phương thức thanh toán đã lưu để tính phí. | `SUBSCRIPTION_HAS_NO_PAYMENT_METHOD` |
| Lỗi gần nhất | Lỗi gần nhất là **soft decline**. Hard decline hoặc lỗi không có mã lỗi được phân loại thì không thể thử lại. | `MANUAL_RETRY_HARD_DECLINE` |
| Không có khoản nào đang xử lý | Không có khoản thanh toán nào trên hóa đơn ở trạng thái `processing` hoặc vẫn chưa có trạng thái được ghi nhận. Khoản thanh toán như vậy là một lần thử, thủ công hoặc tự động, vừa được gửi và chưa báo cáo kết quả. Trước tiên, hãy chờ kết quả của khoản đó. | `MANUAL_RETRY_IN_FLIGHT` |
| Khoản thanh toán mới nhất bị lỗi | Khoản thanh toán mới nhất trên hóa đơn có trạng thái `failed`. Bất kỳ trạng thái nào khác, chẳng hạn như `requires_customer_action`, `requires_payment_method` hoặc `cancelled`, đều chặn việc thử lại ngay cả khi không có khoản nào đang xử lý. | `PREVIOUS_PAYMENT_PENDING` |
| Chưa được thanh toán | Không có khoản thanh toán nào trên hóa đơn thành công. | `MANUAL_RETRY_ALREADY_PAID` |
| Giới hạn thử lại | Có ít hơn 3 lần thử lại thủ công được gửi trên hóa đơn và thời gian chờ đã kết thúc. Xem [Retry Limits](#retry-limits). | `MANUAL_RETRY_LIMIT_REACHED` |
| Customer | Customer không nằm trong [blocklist](/features/customer-blocklist) của bạn. | `PAYMENT_NOT_RETRYABLE` |
| Payment connector | Đối với subscription [BYOP](/features/byop), connector đã được bật. | `BYOP_CONNECTOR_DISABLED` |
| Chế độ live | Trong chế độ live, doanh nghiệp của bạn đã bật các khoản thanh toán live. | `MERCHANT_NOT_LIVE` |

<Note>
  Manual Retry hẹp hơn các lần thử tự động ở một điểm: yêu cầu subscription phải ở trạng thái `on_hold`. Subscription `past_due` đang trong thời gian ân hạn sẽ không đạt điều kiện này. Các lần thử tự động vẫn tiếp tục chạy đối với những trạng thái không hoạt động khác. Xem [Subscription Status Transitions](/features/recovery/payment-retries#subscription-status-transitions).
</Note>

<Warning>
  Thử lại một hard decline trên cùng thẻ không thể thành công và các lần bị từ chối lặp lại sẽ ảnh hưởng xấu đến tỷ lệ ủy quyền của bạn. Khi lý do là `MANUAL_RETRY_HARD_DECLINE`, hãy yêu cầu customer cập nhật phương thức thanh toán thay vì thử lại. [Subscription Dunning](/features/recovery/subscription-dunning) thực hiện việc này tự động.
</Warning>

## Giới hạn thử lại

Mỗi hóa đơn gia hạn cho phép **3** lần thử lại thủ công, với thời gian chờ giữa các lần:

| Lần thử lại thủ công | Khả dụng |
| - | - |
| 1 | Ngay khi khoản thanh toán đủ điều kiện |
| 2 | 1 giờ sau lần đầu tiên |
| 3 | 3 giờ sau lần thứ hai |

Các giới hạn áp dụng cho cả test mode và live mode. Khi giới hạn từ chối một lần thử lại, API trả về `MANUAL_RETRY_LIMIT_REACHED` (HTTP `429`) và nội dung lỗi chỉ chứa `code` cùng `message`. Để biết khi nào lần thử lại tiếp theo mở, [kiểm tra trạng thái thử lại](#check-whether-a-payment-can-be-retried) và đọc `retry_available_at`. Giá trị này là `null` khi cả ba lần thử lại đã được sử dụng.

Các lần thử tự động không được tính vào giới hạn này và các lần thử thủ công không được tính vào 8 lần thử của lịch tự động.

## Thử lại thủ công và tự động

Hai quy trình thử lại khác nhau ở những điểm sau:

| | Manual Retry | Payment Retries |
| - | - | - |
| **Kích hoạt** | Bạn thực hiện từ dashboard hoặc API | Dodo Payments thực hiện theo lịch back-off |
| **Thời điểm** | Ngay lập tức | 12 giờ sau lỗi, sau đó lâu dần theo từng lần |
| **Số lần thử** | 3 lần cho mỗi hóa đơn, với thời gian chờ 1 giờ rồi 3 giờ | Tối đa 8 lần cho mỗi hóa đơn, trong recovery window của bạn |
| **Cần bật Payment Retries** | Không | Có |
| **Ảnh hưởng đến bên kia** | Không. Một lần thử thủ công thất bại không lên lịch hoặc thay đổi lần thử tự động. | Không. Chuỗi tự động tiếp tục bất kể các lần gửi thủ công. |
| **Analytics** | Được tính trong các chỉ số **Payment Retries** dưới **Analytics → Recovery** | Được tính trong cùng các chỉ số |

## Thử lại thông qua API

Trước tiên hãy kiểm tra điều kiện, sau đó gửi lần thử lại. Cả hai endpoint đều nhận ID của một khoản thanh toán bị lỗi.

### Kiểm tra xem có thể thử lại khoản thanh toán hay không

`GET /payments/{payment_id}/retry` không trả về lỗi đối với khoản thanh toán không đủ điều kiện. Thay vào đó, nó trả về `can_retry: false` cùng mã `reason`, để dashboard hoặc công cụ hỗ trợ của riêng bạn có thể hiển thị cùng trạng thái với dashboard Dodo Payments. Endpoint này yêu cầu vai trò **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"
}
```

| Trường | Mô tả |
| - | - |
| `can_retry` | `true` khi một lần thử lại sẽ được gửi ngay lúc này. |
| `reason` | Mã mà lần thử lại sẽ trả về khi thất bại. `null` khi `can_retry` là `true`. |
| `sends_used` | Số lần thử lại thủ công đã được gửi trên hóa đơn này. |
| `sends_allowed` | Luôn là `3`. |
| `retry_available_at` | Thời điểm lần thử lại thủ công tiếp theo mở. `null` khi không còn lần thử lại hoặc khi lý do từ chối không liên quan đến thời gian chờ. |

### Gửi một lần thử lại thủ công

`POST /payments/{payment_id}/retry` tạo một khoản thanh toán mới và tính phí phương thức thanh toán đã lưu. Endpoint này yêu cầu vai trò **Editor**. Các ví dụ SDK sử dụng lại `client` từ ví dụ trước.

<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"
}
```

| Trường | Mô tả |
| - | - |
| `payment_id` | Khoản thanh toán mới được tạo cho lần thử này. |
| `invoice_id` | Hóa đơn gia hạn đã được tính phí. |
| `status` | Kết quả của khoản phí. `processing` nghĩa là payment processor chưa quyết toán khoản phí. `null` nghĩa là chưa ghi nhận kết quả nào trước khi phản hồi được trả về. Trong cả hai trường hợp, các webhook thanh toán sẽ báo cáo kết quả cuối cùng. |
| `retry_attempt` | Vị trí của lần thử này trong số các lần thử lại thủ công trên hóa đơn, bắt đầu từ `1`. |
| `is_manual_retry` | Luôn là `true` trên endpoint này. |
| `sends_used`, `sends_allowed`, `retry_available_at` | Trạng thái giới hạn thử lại sau lần gửi này. `retry_available_at` chỉ là đồng hồ thời gian chờ. Giá trị này được thiết lập ngay cả khi khoản phí thành công; trong trường hợp đó, hóa đơn đã được thanh toán và không có lần thử lại tiếp theo nào được mở. |

### Phản hồi lỗi

Khi không được phép thử lại, `POST` trả về một trong các HTTP status sau:

| HTTP status | Codes | Cách xử lý |
| - | - | - |
| `404` | `NOT_FOUND` | Khoản thanh toán không thuộc doanh nghiệp của bạn. |
| `409` | `MANUAL_RETRY_IN_FLIGHT`, `PREVIOUS_PAYMENT_PENDING`, `CHARGE_NOT_ALLOWED_FOR_SCHEDULED_CANCELLATION` | Việc chặn là tạm thời hoặc cần thay đổi điều gì đó trước. Hãy chờ khoản thanh toán đang xử lý hoặc đang chờ đạt trạng thái cuối cùng, hoặc xóa lịch hủy. |
| `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` | Không thể thử lại khoản thanh toán này. Không lặp lại lệnh gọi. |
| `429` | `MANUAL_RETRY_LIMIT_REACHED` | [Kiểm tra trạng thái thử lại](#check-whether-a-payment-can-be-retried) và chờ đến khi `retry_available_at`, hoặc dừng lại khi cả ba lần thử lại đã được sử dụng. |

Tài liệu tham khảo [Error Codes](/api-reference/error-codes) mô tả mọi mã.

## Webhooks

Manual Retry tạo một khoản thanh toán thông thường, vì vậy các webhook giống như đối với mọi lần thử gia hạn khác sẽ được kích hoạt:

| Event | Kích hoạt khi |
| - | - |
| `payment.succeeded` | Lần thử lại đã được tính phí. `subscription.active` sẽ tiếp theo khi subscription được kích hoạt lại. |
| `payment.failed` | Lần thử lại bị từ chối. Subscription vẫn ở trạng thái `on_hold` và một lần thử thủ công thất bại không lên lịch cho lần thử tự động. |
| `payment.processing` | Processor đã chấp nhận khoản phí nhưng chưa quyết toán. |

Trên payment object trong các event này, `retry_attempt` là `1` hoặc cao hơn và `subscription_id` được thiết lập, giống như đối với một lần thử tự động. Payment object không có trường đánh dấu một lần thử thủ công, vì vậy hãy giữ `payment_id` từ phản hồi thử lại nếu bạn cần phân biệt lần thử thủ công với lần thử theo lịch.

<Card title="Payment Webhook Payloads" icon="webhook" href="/developer-resources/webhooks/intents/payment">
  Schema payload đầy đủ cho các event thanh toán.
</Card>

## Liên quan

<CardGroup cols={2}>
  <Card title="Subscription Payment Retries" icon="arrow-rotate-right" href="/features/recovery/payment-retries">
    Lịch back-off tự động chạy song song với các lần thử lại thủ công.
  </Card>

  <Card title="Subscription Dunning" icon="repeat" href="/features/recovery/subscription-dunning">
    Gửi email cho customer để cập nhật phương thức thanh toán sau một hard decline.
  </Card>

  <Card title="Handle Payment Failures" icon="screwdriver-wrench" href="/developer-resources/handle-payment-failures">
    Đọc các mã từ chối và quyết định khi nào nên thử lại.
  </Card>

  <Card title="Error Codes" icon="triangle-exclamation" href="/api-reference/error-codes">
    Mọi mã `MANUAL_RETRY_*`, trigger và thông báo tương ứng.
  </Card>
</CardGroup>


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