# Callback Pembayaran Keluar (Payout Notification)

Dokumentasi untuk menangani notifikasi callback dari payment gateway untuk pembayaran keluar (payout).

## Deskripsi

Setelah payout diproses dan disetujui, payment gateway akan mengirimkan notifikasi callback untuk memberitahu status payout kepada merchant. Merchant harus memvalidasi dan update status payout di database.

## Metode Request

- **Method**: POST
- **Content-Type**: application/x-www-form-urlencoded
- **URL**: URL yang dikonfigurasi di notifyUrl saat membuat payout

## Parameter Callback

| Nama | Tipe | Deskripsi |
|---|---|---|
| refCode | string | Kode referensi payout (1=success, 2=failed) |
| orderNo | string | Nomor order payout yang dibuat merchant |
| amount | string | Jumlah payout (Rp) |
| dstCode | string | Kode bank/e-wallet tujuan |
| name | string | Nama penerima |
| account | string | Nomor rekening/phone tujuan |
| paidTime | string | Waktu payout berhasil (format: YYYY-MM-DD HH:MM:SS) |
| sign | string | Signature untuk verifikasi (MD5) |

## Nilai refCode

| Code | Status | Arti |
|---|---|---|
| 1 | SUCCESS | Payout berhasil, dana sudah ke tujuan |
| 2 | FAILED | Payout gagal, dana dikembalikan ke merchant |

## Langkah-Langkah Menangani Callback

### 1. Terima Data Callback
```php
<?php
$callbackData = $_POST; // atau $_REQUEST
?>
```

### 2. Verifikasi Signature
```php
<?php
// Hapus parameter sign untuk verifikasi
$sign = $callbackData['sign'];
unset($callbackData['sign']);

// Hitung signature dari data yang diterima
$calculatedSign = generateSignature($callbackData, "150kz9esh1s2f793abb8l0e4mjot3r2c");

// Bandingkan signature
if (strtoupper($sign) !== strtoupper($calculatedSign)) {
    // Signature tidak cocok - reject callback
    exit; // Jangan echo apa-apa
}
?>
```

### 3. Validasi Business Logic
```php
<?php
// Check apakah order payout ada di database
$orderNo = $callbackData['orderNo'];
$dbPayout = getPayoutFromDB($orderNo);

if (!$dbPayout) {
    // Order payout tidak ditemukan
    exit; // Reject callback
}

// Check apakah amount cocok
if ($dbPayout['amount'] != $callbackData['amount']) {
    // Amount tidak cocok - reject
    exit;
}

// Check apakah belum pernah diproses (prevent duplicate)
if ($dbPayout['status'] == 'SUCCESS' || $dbPayout['status'] == 'FAILED') {
    // Sudah diproses sebelumnya, tapi tetap return OK
    echo "OK";
    exit;
}
?>
```

### 4. Update Database Berdasarkan refCode
```php
<?php
if ($callbackData['refCode'] == '1') {
    // Payout sukses
    updatePayoutToSuccess($orderNo, [
        'amount' => $callbackData['amount'],
        'paidTime' => $callbackData['paidTime'],
        'bank' => $callbackData['dstCode'],
        'account' => $callbackData['account']
    ]);
    
    // Kirim notifikasi ke customer
    sendPayoutSuccessEmail($dbPayout['customerEmail']);
    
} else {
    // Payout gagal
    // Dana akan dikembalikan ke saldo merchant secara otomatis
    updatePayoutToFailed($orderNo, [
        'failReason' => 'Payout failed by gateway'
    ]);
    
    // Kirim notifikasi ke customer
    sendPayoutFailureEmail($dbPayout['customerEmail']);
}
?>
```

### 5. Return Response
```php
<?php
// WAJIB mengembalikan "OK" atau "SUCCESS"
echo "OK";
// atau
echo "SUCCESS";
exit;
?>
```

## Contoh Implementasi Lengkap

```php
<?php
// payout_callback.php

// 1. Terima data
$callbackData = $_POST;

// 2. Verifikasi signature
$sign = $callbackData['sign'];
unset($callbackData['sign']);

$calculatedSign = generateSignature($callbackData, "150kz9esh1s2f793abb8l0e4mjot3r2c");
if (strtoupper($sign) !== strtoupper($calculatedSign)) {
    // Log security incident
    logSecurityAlert("Invalid payout callback signature", $callbackData);
    exit;
}

// 3. Validasi & update database
try {
    $orderNo = $callbackData['orderNo'];
    $payout = getPayoutFromDB($orderNo);
    
    if (!$payout) {
        logError("Payout order not found: " . $orderNo);
        exit;
    }
    
    if ($payout['amount'] != $callbackData['amount']) {
        logError("Amount mismatch for payout: " . $orderNo);
        exit;
    }
    
    // Prevent duplicate processing
    if ($payout['status'] != 'PENDING') {
        echo "OK";
        exit;
    }
    
    // Update payout status
    if ($callbackData['refCode'] == '1') {
        updatePayoutStatus($orderNo, 'SUCCESS', $callbackData);
        sendSuccessNotification($payout['customerEmail'], $orderNo);
    } else {
        updatePayoutStatus($orderNo, 'FAILED', $callbackData);
        sendFailureNotification($payout['customerEmail'], $orderNo);
    }
    
    // Log callback
    logCallback("payout", $orderNo, $callbackData['refCode']);
    
    // Return success response
    echo "OK";
    
} catch (Exception $e) {
    logError("Payout callback processing error: " . $e->getMessage());
    exit;
}
?>
```

## Retry Policy

Jika callback tidak mendapat response "OK" atau "SUCCESS":
- **Max Retry**: 5 kali
- **Interval**: Retry otomatis dalam 12 jam
- **After 5 retries**: Notifikasi tidak akan dikirim lagi

Jika Anda perlu resend callback, hubungi support team kami.

## Perbedaan dengan Callback Penerimaan

| Aspek | Penerimaan (Pay-In) | Pembayaran Keluar (Payout) |
|---|---|---|
| Kode field | `refCode` | `refCode` |
| Nomor order | `orderId` | `orderNo` |
| Tujuan | Customer membayar ke merchant | Merchant bayar ke customer |
| Failed handling | Pembayaran dibatalkan | Dana kembali ke saldo merchant |
| Timing | Segera setelah pembayaran | 1-2 jam setelah request |

## Best Practices

1. **Always Verify Signature**: Jangan pernah skip verifikasi signature
2. **Log Semua Callback**: Log setiap callback untuk audit trail
3. **Idempotent Process**: Handle duplicate callback dengan graceful
4. **Notify Customer**: Kirim email/SMS notifikasi ke customer
5. **Fast Response**: Return "OK" dalam < 5 detik
6. **Database Transaction**: Gunakan transaction untuk consistency

---

**Penting**: Jika payout gagal (refCode=2), saldo merchant akan dikembalikan otomatis. Tidak perlu manual refund.
