> ## Documentation Index
> Fetch the complete documentation index at: https://doctpay.o1center.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Payout API

> Kirim dana dari saldo utama merchant ke rekening bank atau e-wallet mana saja.

Kirim dana dari saldo utama merchant ke rekening bank atau e-wallet mana saja, langsung dari request, tanpa perlu rekening terdaftar di dashboard.

## Konfigurasi payout

`GET /api/v1/payout/config`

Tidak memerlukan autentikasi. Mengembalikan biaya admin dan nominal minimum.

```json theme={null}
{
  "success": true,
  "data": {
    "minAmount": 50000,
    "adminFeeFixed": 6500,
    "adminFeePercent": 0,
    "currency": "IDR",
    "note": "amount yang dikirim di request adalah jumlah yang dipotong dari saldo utama (termasuk biaya admin). netAmount = amount - adminFee adalah yang diterima penerima."
  }
}
```

## Daftar tujuan payout

`GET /api/v1/payout/destinations`

Tidak memerlukan autentikasi. Mengembalikan seluruh bank dan e-wallet yang didukung sebagai tujuan payout. Gunakan nilai `dstCode` dari response ini saat memanggil `POST /create`.

```json theme={null}
{
  "success": true,
  "data": [
    { "dstCode": "OVO",      "name": "OVO",                                      "type": "ewallet"  },
    { "dstCode": "DANA",     "name": "DANA",                                     "type": "ewallet"  },
    { "dstCode": "GOPAY",    "name": "GOPAY",                                    "type": "ewallet"  },
    { "dstCode": "BCA",      "name": "PT. BANK CENTRAL ASIA TBK.",               "type": "bankcard" },
    { "dstCode": "BRI",      "name": "PT. BANK RAKYAT INDONESIA (PERSERO)",      "type": "bankcard" },
    { "dstCode": "BNI",      "name": "PT. BANK NEGARA INDONESIA (PERSERO)",      "type": "bankcard" },
    { "dstCode": "MANDIRI",  "name": "PT. BANK MANDIRI (PERSERO) TBK",           "type": "bankcard" },
    "... 80+ bank lainnya"
  ]
}
```

## Buat payout

`POST /api/v1/payout/create`

### Body

| Parameter | Tipe | Wajib | Deskripsi |
| - | - | - | - |
| agentCode | string | Ya | Agent code toko aktif |
| merchantCode | string | Ya | Merchant code toko aktif |
| dstCode | string | Ya | Kode tujuan dari GET /destinations (contoh: BCA, DANA, OVO) |
| accountNumber | string | Ya | Nomor rekening bank atau akun e-wallet tujuan (9–18 digit angka) |
| accountHolder | string | Ya | Nama pemilik rekening atau akun tujuan |
| recipientPhone | string | Ya | Nomor HP penerima, format 08xxx atau 628xxx |
| recipientEmail | string | Ya | Email penerima |
| amount | number | Ya | Nominal dalam Rupiah yang dipotong dari saldo utama (termasuk fee admin, ≥ minimum payout) |
| externalId | string | Tidak | ID unik dari sisi merchant, berfungsi sebagai idempotency key. Request dengan externalId yang sama ditolak |
| description | string | Tidak | Keterangan payout |

### Contoh request

```json Body theme={null}
{
  "agentCode": "AGT-XXXXXXXX",
  "merchantCode": "MERCHANT_CODE",
  "dstCode": "BCA",
  "accountNumber": "1234567890",
  "accountHolder": "Budi Santoso",
  "recipientPhone": "081234567890",
  "recipientEmail": "budi@email.com",
  "amount": 500000,
  "externalId": "WD-20261104-001",
  "description": "Penarikan mingguan"
}
```

```bash cURL theme={null}
curl -X POST http://localhost:5173/api/v1/payout/create \
  -H "Content-Type: application/json" \
  -d '{
    "agentCode": "AGT-XXXXXXXX",
    "merchantCode": "MERCHANT_CODE",
    "dstCode": "BCA",
    "accountNumber": "1234567890",
    "accountHolder": "Budi Santoso",
    "recipientPhone": "081234567890",
    "recipientEmail": "budi@email.com",
    "amount": 500000,
    "externalId": "WD-20261104-001",
    "description": "Penarikan mingguan"
  }'
```

```javascript JavaScript theme={null}
const response = await fetch("http://localhost:5173/api/v1/payout/create", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    agentCode: "AGT-XXXXXXXX",
    merchantCode: "MERCHANT_CODE",
    dstCode: "BCA",
    accountNumber: "1234567890",
    accountHolder: "Budi Santoso",
    recipientPhone: "081234567890",
    recipientEmail: "budi@email.com",
    amount: 500000,
    externalId: "WD-20261104-001",
    description: "Penarikan mingguan",
  }),
});

const { data } = await response.json();
console.log(data.payoutId, data.status, data.netAmount);
```

```php PHP theme={null}
<?php
$payload = [
    "agentCode" => "AGT-XXXXXXXX",
    "merchantCode" => "MERCHANT_CODE",
    "dstCode" => "BCA",
    "accountNumber" => "1234567890",
    "accountHolder" => "Budi Santoso",
    "recipientPhone" => "081234567890",
    "recipientEmail" => "budi@email.com",
    "amount" => 500000,
    "externalId" => "WD-20261104-001",
    "description" => "Penarikan mingguan",
];

$ch = curl_init("http://localhost:5173/api/v1/payout/create");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
    CURLOPT_POSTFIELDS => json_encode($payload),
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true)["data"];
echo $data["payoutId"] . " " . $data["status"];
```

```javascript Node.js theme={null}
import axios from "axios";

const { data: res } = await axios.post("http://localhost:5173/api/v1/payout/create", {
  agentCode: "AGT-XXXXXXXX",
  merchantCode: "MERCHANT_CODE",
  dstCode: "BCA",
  accountNumber: "1234567890",
  accountHolder: "Budi Santoso",
  recipientPhone: "081234567890",
  recipientEmail: "budi@email.com",
  amount: 500000,
  externalId: "WD-20261104-001",
  description: "Penarikan mingguan",
});

console.log(res.data.payoutId, res.data.status, res.data.netAmount);
```

```python Python theme={null}
import requests

payload = {
    "agentCode": "AGT-XXXXXXXX",
    "merchantCode": "MERCHANT_CODE",
    "dstCode": "BCA",
    "accountNumber": "1234567890",
    "accountHolder": "Budi Santoso",
    "recipientPhone": "081234567890",
    "recipientEmail": "budi@email.com",
    "amount": 500000,
    "externalId": "WD-20261104-001",
    "description": "Penarikan mingguan",
}

res = requests.post("http://localhost:5173/api/v1/payout/create", json=payload)
data = res.json()["data"]
print(data["payoutId"], data["status"], data["netAmount"])
```

```go Golang theme={null}
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
)

func main() {
	payload := map[string]any{
		"agentCode":      "AGT-XXXXXXXX",
		"merchantCode":   "MERCHANT_CODE",
		"dstCode":        "BCA",
		"accountNumber":  "1234567890",
		"accountHolder":  "Budi Santoso",
		"recipientPhone": "081234567890",
		"recipientEmail": "budi@email.com",
		"amount":         500000,
		"externalId":     "WD-20261104-001",
		"description":    "Penarikan mingguan",
	}
	body, _ := json.Marshal(payload)

	resp, err := http.Post(
		"http://localhost:5173/api/v1/payout/create",
		"application/json",
		bytes.NewReader(body),
	)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	var result map[string]any
	json.NewDecoder(resp.Body).Decode(&result)
	data := result["data"].(map[string]any)
	fmt.Println(data["payoutId"], data["status"], data["netAmount"])
}
```

```rust Rust theme={null}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }, serde_json = "1", tokio = { version = "1", features = ["full"] }
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let payload = json!({
        "agentCode": "AGT-XXXXXXXX",
        "merchantCode": "MERCHANT_CODE",
        "dstCode": "BCA",
        "accountNumber": "1234567890",
        "accountHolder": "Budi Santoso",
        "recipientPhone": "081234567890",
        "recipientEmail": "budi@email.com",
        "amount": 500000,
        "externalId": "WD-20261104-001",
        "description": "Penarikan mingguan"
    });

    let res: Value = reqwest::Client::new()
        .post("http://localhost:5173/api/v1/payout/create")
        .json(&payload)
        .send()
        .await?
        .json()
        .await?;

    println!("{} {}", res["data"]["payoutId"], res["data"]["status"]);
    Ok(())
}
```

### Contoh response

```json theme={null}
{
  "success": true,
  "data": {
    "payoutId": "WD-A1B2C3D4E5F6",
    "externalId": "WD-20261104-001",
    "amount": 500000,
    "adminFee": 6500,
    "netAmount": 493500,
    "status": "pending",
    "payoutStatus": "pending_provider",
    "description": "Penarikan mingguan",
    "processedAt": null,
    "createdAt": "2026-11-04T09:00:00.000Z",
    "updatedAt": "2026-11-04T09:00:00.000Z",
    "destination": {
      "dstCode": "BCA",
      "accountNumber": "******7890",
      "accountHolder": "Budi Santoso"
    }
  }
}
```

> [!NOTE]
>
> * `amount` adalah nominal yang dipotong dari saldo utama merchant (termasuk biaya admin).
> * `netAmount` adalah yang benar-benar ditransfer ke rekening tujuan (`amount − adminFee`).
> * Contoh: amount Rp 500.000, adminFee Rp 6.500 → netAmount Rp 493.500 masuk ke rekening.
> * Gunakan `GET /payout/config` untuk mengetahui biaya terkini sebelum meminta konfirmasi dari end-user.

### Status payout

| status | payoutStatus | Arti |
| - | - | - |
| `pending` | `pending_provider` | Payout sedang diproses oleh provider bank. |
| `approved` | `success` | Transfer berhasil masuk ke rekening tujuan. |
| `rejected` | `failed` | Transfer gagal, saldo dikembalikan (refund otomatis). |
| `cancelled` | - | Dibatalkan oleh merchant atau admin sebelum diproses. |

### Keamanan

* Autentikasi menggunakan `agentCode` + `merchantCode`, sama seperti Payments API.
* IP Whitelist berlaku: jika aktif, hanya IP terdaftar yang dapat memanggil endpoint ini.
* Pemotongan saldo dilakukan secara atomic untuk mencegah race condition double-spend.
* Gunakan `externalId` sebagai idempotency key untuk mencegah duplikasi request payout.
* Jika provider gagal, saldo dikembalikan secara otomatis (refund). Tidak ada dana yang hilang.

## Detail payout

`GET /api/v1/payout/:payoutId`

Ambil detail satu payout berdasarkan Payout ID.

| Parameter | Lokasi | Tipe | Wajib | Deskripsi |
| - | - | - | - | - |
| payoutId | path | string | Ya | payoutId dari response create payout |
| agentCode | query | string | Ya | Agent code toko aktif |
| merchantCode | query | string | Ya | Merchant code toko aktif |

## Daftar payout

`GET /api/v1/payout`

Daftar semua payout milik toko, dengan filter status dan paginasi.

| Parameter | Lokasi | Tipe | Wajib | Deskripsi |
| - | - | - | - | - |
| agentCode | query | string | Ya | Agent code toko aktif |
| merchantCode | query | string | Ya | Merchant code toko aktif |
| status | query | string | Tidak | Filter status: pending, approved, rejected, atau cancelled |
| limit | query | number | Tidak | Jumlah data per halaman, 1–100 (default: 20) |
| offset | query | number | Tidak | Jumlah data yang dilewati (default: 0) |


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