# Sahkan callback dan checksum

> Cara callback dan return URL Bayarcash berfungsi, dan cara menandatangani request dan mengesahkan callback dengan checksum HMAC-SHA256.
>
> Source: https://docs.bayarcash.com/ms/callbacks-and-checksum/

Apabila bayaran selesai, Bayarcash memaklumkan server anda melalui **callback** dan menghantar pelanggan kembali melalui **return URL**. Setiap callback membawa `checksum`. Semak checksum itu dengan API Secret Key anda sebelum anda mempercayai data itu. Anda juga boleh menambah checksum pada request anda supaya Bayarcash tahu request itu tidak diubah.

## Callback URL & return URL

Anda menetapkan kedua-dua URL pada setiap payment intent (`callback_url` dan `return_url`). Fungsi kedua-duanya berbeza:

| | Callback URL | Return URL |
|---|---|---|
| Siapa yang memanggilnya | Server Bayarcash, terus ke server anda | Browser pelanggan anda, selepas Bayarcash membuat redirect |
| Cara ia sampai | POST | GET dengan field dalam query string (API v3) |
| Boleh dipercayai untuk status pesanan? | Ya, selepas anda menyemak checksum | Tidak. Pelanggan boleh menutup tab atau menukar URL |
| Guna untuk | Tandakan pesanan sebagai sudah dibayar atau gagal | Paparkan page keputusan kepada pelanggan |

Kemas kini pesanan anda daripada callback sahaja. Jika return URL sampai dahulu, paparkan mesej "bayaran sedang disahkan" dan tunggu callback. Anda juga boleh menyemak transaksi itu dengan API.

## Callback yang Bayarcash hantar

Setiap callback ada field `record_type` yang menunjukkan jenis callback itu:

| Callback | Bila ia dihantar |
|---|---|
| Pre-transaction | Pada awal proses bayaran, sebelum rekod transaksi muktamad. |
| Transaction | Apabila bayaran mencapai status akhir (berjaya, gagal, dibatalkan dan sebagainya). |
| FPX Direct Debit authorization | Apabila pelanggan melengkapkan atau meninggalkan enrolment e-Mandate. |
| FPX Direct Debit bank approval | Apabila bank pelanggan meluluskan atau menolak mandate. Callback ini boleh sampai beberapa hari kemudian. |
| FPX Direct Debit transaction | Untuk setiap collection yang diambil daripada mandate yang aktif. |

Field `status` dalam callback transaksi ialah nombor:

| Status | Maksud |
|---|---|
| 0 | New |
| 1 | Pending |
| 2 | Unsuccessful |
| 3 | Successful |
| 4 | Cancelled |
| 5 | Abandoned |

## Balas setiap callback dengan HTTP 200

Callback URL anda mesti membalas **HTTP 200** selepas ia menyemak dan menyimpan keputusan. Jika ia membalas dengan status lain, atau tidak membalas, Bayarcash akan menghantar callback itu semula. Jadi, handler anda mesti selamat untuk dijalankan dua kali. Sebelum anda memproses pesanan, semak sama ada anda sudah memproses `transaction_id` itu.

Callback dihantar sebagai POST. Plugin dan SDK Bayarcash membaca callback sebagai form field (`$_POST` dalam PHP). Jika framework anda menerima `application/json`, baca body JSON.

## Cara checksum berfungsi

Peraturan yang sama digunakan untuk menandatangani request dan mengesahkan callback. Hanya senarai field yang berbeza bagi setiap panggilan. Untuk membina checksum:

1. Ambil field bagi panggilan itu (lihat jadual di bawah), dengan nilai tepat seperti yang dihantar atau diterima. Guna nilai kosong untuk field yang tiada.
2. Susun field mengikut nama field, A hingga Z.
3. Gabungkan nilai mengikut susunan itu, dengan `|` di antara setiap nilai.
4. Kira HMAC-SHA256 bagi teks itu, dengan API Secret Key anda sebagai key. Hasilnya, dalam hex huruf kecil, ialah checksum.

Untuk callback, bandingkan hasil anda dengan field `checksum` dalam callback itu. Guna perbandingan constant-time, seperti `hash_equals()` dalam PHP atau `hmac.compare_digest()` dalam Python. Jika kedua-duanya berbeza, tolak callback itu.

Checksum pada request adalah pilihan, tetapi kami mengesyorkannya. Setiap callback sentiasa ada checksum, jadi sentiasa sahkan checksum itu.

## Field dalam setiap checksum

Setiap panggilan menggunakan senarai field sendiri:

| Panggilan | Field dalam checksum |
|---|---|
| Cipta payment intent (request) | `payment_channel`, `order_number`, `amount`, `payer_name`, `payer_email` |
| Enrolment FPX Direct Debit (request) | `order_number`, `amount`, `payer_name`, `payer_email`, `payer_telephone_number`, `payer_id_type`, `payer_id`, `application_reason`, `frequency_mode` |
| Callback pre-transaction | `record_type`, `exchange_reference_number`, `order_number` |
| Callback transaction | `record_type`, `transaction_id`, `exchange_reference_number`, `exchange_transaction_id`, `order_number`, `currency`, `amount`, `payer_name`, `payer_email`, `payer_bank_name`, `status`, `status_description`, `datetime` |
| Return URL | `transaction_id`, `exchange_reference_number`, `exchange_transaction_id`, `order_number`, `currency`, `amount`, `payer_bank_name`, `status`, `status_description` |
| Callback FPX Direct Debit bank approval | `record_type`, `approval_date`, `approval_status`, `mandate_id`, `mandate_reference_number`, `order_number`, `payer_bank_code_hashed`, `payer_bank_code`, `payer_bank_account_no`, `application_type` |
| Callback FPX Direct Debit transaction | `record_type`, `batch_number`, `mandate_id`, `mandate_reference_number`, `transaction_id`, `datetime`, `reference_number`, `amount`, `status`, `status_description`, `cycle` |

Jadual ini menyenaraikan field sahaja. Susunannya ditentukan oleh langkah 2. Jika payment intent ada lebih daripada satu `payment_channel`, gabungkan nombor saluran dengan koma (`1,5`) sebelum anda menambahnya, seperti yang PHP SDK buat. Untuk request maintenance dan callback authorization FPX Direct Debit, guna method pengesahan dalam [SDK rasmi](/ms/sdks/). Method itu ada senarai field yang tepat.

## Contoh: sahkan callback transaksi dalam PHP

Kod ini menyemak callback transaksi tanpa SDK. Dengan [PHP SDK](/ms/php-sdk/), `verifyTransactionCallbackData()` membuat semakan yang sama dalam satu baris.

```php
function bayarcash_checksum(array $data, array $fields, string $secretKey): string
{
    $payload = [];
    foreach ($fields as $field) {
        $payload[$field] = $data[$field] ?? '';
    }
    ksort($payload);

    return hash_hmac('sha256', implode('|', $payload), $secretKey);
}

$fields = [
    'record_type', 'transaction_id', 'exchange_reference_number',
    'exchange_transaction_id', 'order_number', 'currency', 'amount',
    'payer_name', 'payer_email', 'payer_bank_name', 'status',
    'status_description', 'datetime',
];

$callback = $_POST;
$expected = bayarcash_checksum($callback, $fields, getenv('BAYARCASH_API_SECRET_KEY'));

if (! hash_equals($expected, (string) ($callback['checksum'] ?? ''))) {
    http_response_code(403);
    exit;
}

// Checksum is valid: save the result, then answer 200.
http_response_code(200);
```

## Contoh: tandatangani payment intent dalam Python

Peraturan yang sama dalam Python, untuk request payment intent:

```python

def bayarcash_checksum(values: dict, secret_key: str) -> str:
    joined = "|".join(str(values[k]) for k in sorted(values))
    return hmac.new(secret_key.encode(), joined.encode(), hashlib.sha256).hexdigest()

checksum = bayarcash_checksum({
    "payment_channel": "1",
    "order_number": "INV-1001",
    "amount": "10.00",
    "payer_name": "Ahmad bin Abdullah",
    "payer_email": "ahmad@example.com",
}, "YOUR_API_SECRET_KEY")
```

Hantar hasilnya sebagai `checksum` bersama field yang lain. Rujukan penuh untuk field ada dalam [panduan checksum dalam rujukan API](https://api-reference.bayarcash.com/#tag/checksum).

## Tips

- **Guna HTTPS untuk callback URL dan return URL anda.** Callback membawa butiran bayaran, dan HTTPS melindungi butiran itu semasa dihantar.
- **Simpan API Secret Key di server anda sahaja.** Sesiapa yang ada key ini boleh memalsukan callback yang lulus semakan checksum.
- **Guna pakej framework jika ada untuk framework anda.** Pakej Laravel dan Django menambah route callback yang menyemak checksum dan menolak checksum yang salah dengan HTTP 403.

## Isu biasa

### Kenapa log WooCommerce saya memaparkan "Invalid checksum for order"?

Checksum tidak sepadan. Biasanya API Secret Key salah, atau key itu dari environment yang lain (key sandbox pada callback production, atau sebaliknya). Copy key itu semula dari merchant dashboard yang anda gunakan.

### Kenapa checksum saya tidak pernah sepadan walaupun key saya betul?

Guna senarai field yang tepat untuk panggilan itu, susun mengikut nama field, dan guna nilai tepat seperti yang dihantar atau diterima. 10.00 dan 10 menghasilkan checksum yang berbeza. Field yang tiada dikira sebagai nilai kosong. Jangan langkau field itu.

### Kenapa callback yang sama sampai dua kali?

Bayarcash menghantar callback semula apabila server anda tidak membalas dengan HTTP 200. Balas dengan 200 selepas anda menyimpan keputusan. Semak juga ID transaksi supaya anda tidak memproses bayaran yang sama dua kali.
