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

# Coba Lagi Pembayaran Manual

> Coba lagi pembayaran perpanjangan langganan yang gagal sesuai permintaan dari dashboard atau API, tanpa menunggu percobaan otomatis berikutnya.

<Info>
  Manual Retry mencoba kembali pembayaran **perpanjangan** langganan yang gagal saat Anda memintanya, dari halaman detail pembayaran atau melalui API. Fitur ini membebankan metode pembayaran yang tersimpan pada langganan, dan berjalan secara independen dari jadwal [Payment Retries](/features/recovery/payment-retries) otomatis.
</Info>

## Apa Itu Coba Lagi Manual?

Saat pembayaran perpanjangan gagal, langganan berpindah ke `on_hold`. Jika Anda mengaktifkan [Payment Retries](/features/recovery/payment-retries), fitur tersebut akan mencoba kembali membebankan pembayaran sesuai jadwal back-off. Terkadang Anda mengetahui bahwa pembayaran akan berhasil saat ini: pelanggan telah mengonfirmasi bahwa mereka telah mengisi saldo akun, atau tim dukungan Anda sedang menghubungi mereka. Manual Retry langsung mengirimkan satu percobaan, tanpa menunggu berjam-jam atau berhari-hari hingga percobaan terjadwal berikutnya.

* **Hanya pembayaran perpanjangan**: Manual Retry berlaku untuk invoice perpanjangan langganan saat langganan berada dalam status `on_hold`. Pembayaran pertama, pembayaran satu kali, biaya perubahan paket, dan biaya on-demand tidak memenuhi syarat.
* **Tanpa tindakan pelanggan**: Pembebanan dilakukan ke metode pembayaran yang sudah tersimpan pada langganan.
* **Independen dari percobaan otomatis**: Percobaan manual tidak menggunakan jatah percobaan dari jadwal otomatis, tidak mengubah waktu percobaan terjadwal berikutnya, dan tetap berfungsi meskipun Payment Retries dinonaktifkan.
* **Mencoba kembali invoice, bukan pembayaran**: Pembayaran yang gagal hanyalah titik awal. Dodo Payments menemukan invoice perpanjangan terbuka di baliknya dan membebankan utang tersebut, sehingga tidak masalah pembayaran yang gagal pada invoice mana yang Anda gunakan untuk mencoba kembali.

## Mencoba Lagi dari Dashboard

Hanya pengguna dengan peran Owner atau Editor yang dapat mengirimkan percobaan manual.

<Steps>
  <Step title="Open the failed payment">
    Buka **Transactions → Payments**, lalu klik pembayaran perpanjangan yang gagal untuk membuka halaman **Transaction details**.
  </Step>

  <Step title="Click Retry Payment Manually">
    Klik **Retry Payment Manually** di sudut kanan atas. Tombol ini hanya tersedia saat pembayaran [memenuhi syarat](#eligibility).
  </Step>

  <Step title="Check the result">
    Dodo Payments membuat pembayaran baru untuk percobaan tersebut, dan pembayaran itu muncul di **Activity Log**. Jika pembebanan berhasil, langganan kembali ke `active`, dan tanggal penagihan berikutnya bergeser ke satu periode penagihan setelah percobaan ulang yang berhasil. Jika pemroses pembayaran belum menyelesaikan pembebanan, pembayaran akan ditampilkan sebagai sedang berlangsung hingga webhook `payment.succeeded` atau `payment.failed` melaporkan hasilnya.
  </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="Transaction details page for a failed payment showing the error code and message, an Activity Log, and a Retry Payment Manually button" style={{ maxHeight: '500px', width: 'auto' }} width="1285" height="698" data-path="images/recovery/manual-retry-transaction-details.png" />
</Frame>

## Kelayakan

Dodo Payments hanya mengirimkan percobaan manual jika semua pemeriksaan dalam tabel ini terpenuhi. Kolom **Reason code** adalah nilai yang dikembalikan API: di `reason` pada `GET /payments/{payment_id}/retry`, dan sebagai error `code` pada `POST /payments/{payment_id}/retry`.

| Pemeriksaan | Persyaratan | Reason code |
| - | - | - |
| Jenis pembayaran | Pembayaran **perpanjangan** langganan yang invoice-nya masih terbuka. Pembayaran tanpa invoice, pembayaran pertama, pembayaran satu kali, biaya perubahan paket, dan biaya on-demand tidak dapat dicoba kembali. | `PAYMENT_NOT_RETRYABLE` |
| Status langganan | `on_hold` | `SUBSCRIPTION_INACTIVE` |
| Pembatalan terjadwal | Langganan tidak dijadwalkan untuk dibatalkan pada tanggal penagihan berikutnya. | `CHARGE_NOT_ALLOWED_FOR_SCHEDULED_CANCELLATION` |
| Metode pembayaran tersimpan | Langganan memiliki metode pembayaran tersimpan yang dapat dibebankan. | `SUBSCRIPTION_HAS_NO_PAYMENT_METHOD` |
| Kegagalan terakhir | Kegagalan terbaru adalah **soft decline**. Hard decline atau kegagalan tanpa kode error yang diklasifikasikan tidak dapat dicoba kembali. | `MANUAL_RETRY_HARD_DECLINE` |
| Tidak ada proses yang sedang berjalan | Tidak ada pembayaran pada invoice yang berstatus `processing` atau masih tidak memiliki status tercatat. Pembayaran tersebut merupakan percobaan, manual atau otomatis, yang baru saja dikirim dan belum memberikan hasil. Tunggu hasilnya terlebih dahulu. | `MANUAL_RETRY_IN_FLIGHT` |
| Pembayaran terbaru gagal | Pembayaran terbaru pada invoice memiliki status `failed`. Status lain apa pun, seperti `requires_customer_action`, `requires_payment_method`, atau `cancelled`, akan memblokir percobaan ulang meskipun tidak ada proses yang sedang berjalan. | `PREVIOUS_PAYMENT_PENDING` |
| Belum dibayar | Tidak ada pembayaran pada invoice yang berhasil. | `MANUAL_RETRY_ALREADY_PAID` |
| Batas percobaan | Kurang dari 3 percobaan manual telah dikirim pada invoice, dan masa cooldown telah berlalu. Lihat [Retry Limits](#retry-limits). | `MANUAL_RETRY_LIMIT_REACHED` |
| Pelanggan | Pelanggan tidak berada dalam [blocklist](/features/customer-blocklist) Anda. | `PAYMENT_NOT_RETRYABLE` |
| Payment connector | Untuk langganan [BYOP](/features/byop), connector telah diaktifkan. | `BYOP_CONNECTOR_DISABLED` |
| Live mode | Dalam live mode, bisnis Anda telah mengaktifkan pembayaran live. | `MERCHANT_NOT_LIVE` |

<Note>
  Manual Retry lebih terbatas daripada percobaan otomatis dalam satu hal: fitur ini mengharuskan langganan berada dalam status `on_hold`. Langganan `past_due` dalam masa tenggang tidak memenuhi pemeriksaan ini. Percobaan otomatis tetap berjalan untuk status nonaktif lainnya. Lihat [Subscription Status Transitions](/features/recovery/payment-retries#subscription-status-transitions).
</Note>

<Warning>
  Mencoba kembali hard decline pada kartu yang sama tidak akan berhasil, dan penolakan berulang dapat menurunkan tingkat otorisasi Anda. Jika alasannya adalah `MANUAL_RETRY_HARD_DECLINE`, minta pelanggan memperbarui metode pembayaran mereka. [Subscription Dunning](/features/recovery/subscription-dunning) melakukan hal ini secara otomatis.
</Warning>

## Batas Percobaan

Setiap invoice perpanjangan mengizinkan **3** percobaan manual, dengan masa cooldown di antaranya:

| Percobaan manual | Tersedia |
| - | - |
| 1 | Segera setelah pembayaran memenuhi syarat |
| 2 | 1 jam setelah percobaan pertama |
| 3 | 3 jam setelah percobaan kedua |

Batas ini berlaku dalam test mode dan live mode. Saat batas menolak percobaan, API mengembalikan `MANUAL_RETRY_LIMIT_REACHED` (HTTP `429`), dan isi error hanya memuat `code` serta `message`. Untuk mengetahui kapan percobaan berikutnya tersedia, [periksa status percobaan](#check-whether-a-payment-can-be-retried) dan baca `retry_available_at`. Nilainya adalah `null` setelah ketiga percobaan digunakan.

Percobaan otomatis tidak dihitung dalam batas ini, dan percobaan manual tidak dihitung dalam 8 percobaan pada jadwal otomatis.

## Percobaan Manual vs. Otomatis

Kedua jalur percobaan ini berbeda dalam hal-hal berikut:

| | Manual Retry | Payment Retries |
| - | - | - |
| **Pemicu** | Anda, dari dashboard atau API | Dodo Payments, berdasarkan jadwal back-off |
| **Waktu** | Segera | 12 jam setelah kegagalan, kemudian semakin lama |
| **Percobaan** | 3 per invoice, dengan cooldown 1 jam lalu 3 jam | Hingga 8 per invoice, dalam jendela pemulihan Anda |
| **Memerlukan Payment Retries aktif** | Tidak | Ya |
| **Dampak pada yang lain** | Tidak ada. Kegagalan manual tidak menjadwalkan atau memindahkan percobaan otomatis. | Tidak ada. Rangkaian otomatis tetap berjalan terlepas dari pengiriman manual. |
| **Analitik** | Dihitung dalam metrik **Payment Retries** di **Analytics → Recovery** | Dihitung dalam metrik yang sama |

## Mencoba Kembali melalui API

Periksa kelayakan terlebih dahulu, lalu kirimkan percobaan ulang. Kedua endpoint menerima ID pembayaran yang gagal.

### Memeriksa Apakah Pembayaran Dapat Dicoba Kembali

`GET /payments/{payment_id}/retry` tidak mengembalikan error untuk pembayaran yang tidak memenuhi syarat. Endpoint ini mengembalikan `can_retry: false` dengan kode `reason`, sehingga dashboard atau alat dukungan Anda dapat menampilkan status yang sama seperti dashboard Dodo Payments. Endpoint ini memerlukan peran **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"
}
```

| Field | Deskripsi |
| - | - |
| `can_retry` | `true` ketika percobaan akan dikirim saat ini. |
| `reason` | Kode yang akan menyebabkan percobaan gagal. `null` ketika `can_retry` bernilai `true`. |
| `sends_used` | Jumlah percobaan manual yang telah dikirim pada invoice ini. |
| `sends_allowed` | Selalu `3`. |
| `retry_available_at` | Waktu percobaan manual berikutnya tersedia. `null` ketika tidak ada percobaan tersisa atau ketika penolakan tidak berkaitan dengan cooldown. |

### Mengirimkan Percobaan Manual

`POST /payments/{payment_id}/retry` membuat pembayaran baru dan membebankan metode pembayaran yang tersimpan. Endpoint ini memerlukan peran **Editor**. Contoh SDK menggunakan kembali `client` dari contoh sebelumnya.

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

| Field | Deskripsi |
| - | - |
| `payment_id` | Pembayaran baru yang dibuat untuk percobaan ini. |
| `invoice_id` | Invoice perpanjangan yang dibebankan. |
| `status` | Hasil pembebanan. `processing` berarti pemroses belum menyelesaikannya. `null` berarti tidak ada hasil yang tercatat sebelum respons dikembalikan. Dalam kedua kasus, webhook pembayaran akan melaporkan hasil akhirnya. |
| `retry_attempt` | Posisi percobaan ini di antara percobaan manual pada invoice, dimulai dari `1`. |
| `is_manual_retry` | Selalu `true` pada endpoint ini. |
| `sends_used`, `sends_allowed`, `retry_available_at` | Status batas percobaan setelah pengiriman ini. `retry_available_at` hanya merupakan jam cooldown. Nilai ini ditetapkan bahkan ketika pembebanan berhasil, yang berarti invoice telah dibayar dan tidak ada percobaan berikutnya yang tersedia. |

### Respons Error

Saat percobaan tidak diizinkan, `POST` mengembalikan salah satu status HTTP berikut:

| Status HTTP | Kode | Tindakan |
| - | - | - |
| `404` | `NOT_FOUND` | Pembayaran tersebut bukan milik bisnis Anda. |
| `409` | `MANUAL_RETRY_IN_FLIGHT`, `PREVIOUS_PAYMENT_PENDING`, `CHARGE_NOT_ALLOWED_FOR_SCHEDULED_CANCELLATION` | Pemblokiran bersifat sementara, atau ada hal lain yang harus diubah terlebih dahulu. Tunggu pembayaran yang sedang berjalan atau tertunda mencapai status akhir, atau hapus pembatalan terjadwal. |
| `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` | Pembayaran ini tidak dapat dicoba kembali. Jangan ulangi panggilan tersebut. |
| `429` | `MANUAL_RETRY_LIMIT_REACHED` | [Periksa status percobaan](#check-whether-a-payment-can-be-retried) dan tunggu hingga `retry_available_at`, atau berhenti setelah ketiga percobaan digunakan. |

Referensi [Error Codes](/api-reference/error-codes) menjelaskan setiap kode.

## Webhook

Manual Retry membuat pembayaran biasa, sehingga webhook yang sama akan aktif seperti pada percobaan perpanjangan lainnya:

| Event | Aktif ketika |
| - | - |
| `payment.succeeded` | Percobaan ulang telah dibebankan. `subscription.active` menyusul ketika langganan diaktifkan kembali. |
| `payment.failed` | Percobaan ulang ditolak. Langganan tetap berada dalam status `on_hold`, dan kegagalan manual tidak menjadwalkan percobaan otomatis. |
| `payment.processing` | Pemroses telah menerima pembebanan tetapi belum menyelesaikannya. |

Pada objek pembayaran dalam event ini, `retry_attempt` bernilai `1` atau lebih tinggi dan `subscription_id` ditetapkan, sama seperti pada percobaan otomatis. Objek pembayaran tidak memiliki field yang menandai percobaan manual, jadi simpan `payment_id` dari respons percobaan jika Anda perlu membedakan percobaan manual dari percobaan terjadwal.

<Card title="Payment Webhook Payloads" icon="webhook" href="/developer-resources/webhooks/intents/payment">
  Skema payload lengkap untuk event pembayaran.
</Card>

## Terkait

<CardGroup cols={2}>
  <Card title="Subscription Payment Retries" icon="arrow-rotate-right" href="/features/recovery/payment-retries">
    Jadwal back-off otomatis yang berjalan bersamaan dengan percobaan manual.
  </Card>

  <Card title="Subscription Dunning" icon="repeat" href="/features/recovery/subscription-dunning">
    Kirim email kepada pelanggan untuk memperbarui metode pembayaran mereka setelah hard decline.
  </Card>

  <Card title="Handle Payment Failures" icon="screwdriver-wrench" href="/developer-resources/handle-payment-failures">
    Baca kode penolakan dan tentukan kapan percobaan ulang layak dilakukan.
  </Card>

  <Card title="Error Codes" icon="triangle-exclamation" href="/api-reference/error-codes">
    Setiap kode `MANUAL_RETRY_*`, pemicunya, dan pesannya.
  </Card>
</CardGroup>


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