# Payload webhook payment form

> JSON yang BCL hantar ke URL webhook anda apabila pelanggan menghantar payment form, atau bayaran berjaya atau gagal, ruangan demi ruangan.
>
> Source: https://docs.bcl.my/ms/webhook-payment-form/

Webhook membolehkan BCL memberitahu sistem anda sendiri tentang sesuatu jualan sebaik sahaja ia berlaku. Apabila pelanggan menghantar payment form, atau bayaran mereka berjaya atau gagal, BCL menghantar HTTP `POST` dengan body JSON ke setiap URL yang anda tetapkan. Page ini menyenaraikan setiap ruangan dalam body itu.

Untuk API BCL yang penuh, lihat [rujukan API](https://bcl.my/docs/api).

## Tetapkan URL webhook anda

Anda menetapkan webhook untuk setiap payment form. Untuk on-kan webhook:

1. Pergi ke **Payments** → **Forms** dan buka payment form anda.
2. Klik **Advanced** di bahagian atas.

   *(Screenshot: Tab Advanced di bahagian atas page Edit Payment Form, ditanda)*

3. Buka tab **Webhook Settings** dan on-kan **Enable Webhook**.

   *(Screenshot: Tab Webhook Settings dengan Enable Webhook on, satu URL webhook dan tiga event ditanda)*

4. Di bawah **Webhook URLs**, masukkan endpoint anda. Jika tiada kotak dipaparkan, klik **Add Webhook URL** dahulu. Klik **Add Webhook URL** sekali lagi untuk menambah lebih banyak URL, sehingga 10. Setiap URL mesti bermula dengan `https://`, dan setiap URL menerima event yang sama.
5. Di bawah **Webhook Events**, tandakan event yang anda mahu.
6. Klik **Save Advanced Settings**.

Untuk mencuba endpoint anda tanpa jualan sebenar, klik **Send Test** dalam bahagian **Webhook Configuration**. Butang ini dipaparkan selepas webhook di-on-kan dan ada URL. Pilih **Event Type**, semak **Webhook URL** dan **Payload Preview**, kemudian klik **Send Test Webhook**. Payload test menggunakan nilai contoh rawak.

*(Screenshot: Panel Send Test Webhook dengan Payment Success dipilih, payload preview dan butang Send Test Webhook ditanda)*

BCL kemudian memaparkan **Test Webhook Sent** dengan kod status endpoint anda, atau **Webhook Failed** dengan status dan permulaan reply-nya. Webhook test tidak disenaraikan dalam **Webhook Logs**.

Event form dan booking form ada tab **Webhook Settings** yang sama di bawah **Advanced**, dengan payload bayaran yang sama seperti di page ini.

## Event

Setiap body request ada dua key: `event` (nama event) dan `data` (payload). Event ini menggunakan payload di page ini:

| Event | Bila BCL menghantarnya |
| --- | --- |
| `form-submit` | Pelanggan menghantar payment form, sebelum bayaran. |
| `payment-success` | Bayarcash mengesahkan bayaran. |
| `payment-failed` | Bayaran gagal atau dibatalkan. |

Untuk payment form Direct Debit, lihat [Payload webhook Direct Debit](/ms/webhook-direct-debit/). Event form juga boleh menghantar `ticket-checked-in`, dan booking form pula `booking-confirmed` dan `booking-cancelled`. Event ini menggunakan payload yang berbeza.

## Contoh payload

Ini ialah request `payment-success`. Nilai-nilainya rekaan.

```json
{
  "event": "payment-success",
  "data": {
    "formable_type": "payment_form",
    "formable_id": 210,
    "form_title": "Online Cooking Class",
    "form_url": "https://shop.example.com/form/online-cooking-class",
    "form_featured_image": "https://cdn.example.com/banner.jpg",
    "record_type": "Transaction",
    "record_id": "LINK-02046",
    "main_data": {
      "id": "01jfps609t08vjb8d5exq27bz6",
      "payer_name": "Ali bin Abu",
      "payer_email": "ali@example.com",
      "payer_telephone_number": "+60123456789",
      "order_number": "LINK-02046",
      "transaction_type": "live",
      "currency": "MYR",
      "subtotal_amount": "100.00",
      "fee_amount": "1.00",
      "amount": "101.00",
      "payment_channel": "FPX",
      "status": 3,
      "status_description": "Approved",
      "created_at": "27/09/2026 14:05:09",
      "customer_id": "01jfps5zq1m2k8c4x7v9b3n6td",
      "is_paid": 1,
      "retry_count": 0,
      "source_type": "payment_form",
      "items": [
        {
          "id": 5501,
          "index": 101,
          "productable_type": "product_item",
          "productable_id": 101,
          "item": "Cooking Class Recording",
          "sku": "CC-001",
          "option": "",
          "quantity": 1,
          "unit_amount": "100.00",
          "normal_price": "120.00",
          "sale_price": "100.00",
          "amount": "100.00",
          "access_url": null,
          "weight": "0.00",
          "total_weight": "0.00",
          "metadata": null,
          "product_image": "https://cdn.example.com/class.jpg",
          "protected_content": [
            {
              "title": "Module 1",
              "url": "https://shop.example.com/content/abc123token",
              "access_finder_url": "https://shop.example.com/content/7/access"
            }
          ]
        }
      ]
    },
    "tracking_params": {
      "utm_source": "facebook",
      "utm_campaign": "september-promo"
    },
    "receipt_url": "https://bcl.my/storage/pdf/LINK-02046.pdf",
    "custom_fields_data": {
      "address_line1": "1 Jalan Contoh",
      "city": "Kajang",
      "state": "Selangor",
      "postal_code": "43000",
      "country": "MY"
    },
    "affiliate_data": {
      "referral_id": 57,
      "affiliate_id": 12,
      "affiliate_username": "siti",
      "affiliate_name": "Siti binti Ahmad",
      "affiliate_email": "siti@example.com",
      "affiliate_phone": "+60198765432",
      "commission_type": "percentage",
      "commission_rate": "10.00",
      "commission_amount": "10.00",
      "commission_breakdown": null,
      "status": "approved",
      "referral_source": "direct"
    }
  }
}
```

## Ruangan peringkat atas

Objek `data` mengandungi key berikut:

| Ruangan | Jenis | Maksud |
| --- | --- | --- |
| `formable_type` | string | Jenis form: `payment_form`, `event_form` atau `booking_form`. |
| `formable_id` | number | ID payment form. Menggantikan `form_id` yang lama. |
| `form_title` | string | Tajuk form. Untuk event form, nama event itu. |
| `form_url` | string | Link awam ke payment form. Kosong jika payment form itu tiada slug atau domain. |
| `form_featured_image` | string | URL featured image, atau kosong. |
| `record_type` | string | Sentiasa `Transaction` di page ini. |
| `record_id` | string | Nombor pesanan BCL, contohnya `LINK-02046`. Guna nombor ini untuk memadankan rekod. |
| `main_data` | object | Pesanan itu. Lihat bahagian seterusnya. |
| `tracking_params` | object atau null | UTM dan click ID yang direkodkan semasa pelanggan membuka payment form: `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`, `utm_id`, `fbclid`, `ttclid`, `gclid`. Hanya yang ada dihantar. |
| `receipt_url` | string | `payment-success` sahaja. Link ke resit PDF. Sesiapa yang membukanya perlu mengesahkan dengan kod sekali guna dahulu. |
| `custom_fields_data` | object | Custom field dan ruangan alamat payment form, dengan nama ruangan sebagai key. Tiada jika payment form itu tidak ada ruangan tersebut. |
| `affiliate_data` | object | Hanya ada apabila affiliate yang membawa jualan itu. |

## Ruangan pesanan (main_data)

BCL menghantar rekod pesanan dan membuang nilai kosong. Ruangan tanpa nilai (null, kosong, atau `0.00` untuk kebanyakan amaun) tidak dihantar, jadi jangan bergantung pada setiap ruangan sentiasa ada.

| Ruangan | Jenis | Maksud |
| --- | --- | --- |
| `id` | string | ID transaksi dalaman. |
| `payer_name`, `payer_email`, `payer_telephone_number` | string | Butiran pembeli. |
| `order_number` | string | Sama seperti `record_id`. |
| `transaction_type` | string | `live` atau `test`. |
| `currency` | string | Biasanya `MYR`. |
| `subtotal_amount` | string | Jumlah item sebelum caj. Sentiasa dihantar. |
| `discount_amount` | string | Diskaun kupon. |
| `coupon_code`, `coupon_usage_id` | string, number | Kupon yang digunakan. |
| `fee_amount` | string | Caj yang dikenakan kepada pembeli. Sentiasa dihantar. |
| `additional_fee_amount` | string | Caj tambahan yang ditetapkan pada payment form. |
| `rounding_adjustment` | string | Pembundaran yang dikenakan pada jumlah. |
| `tax_amount` | object | Butiran cukai, seperti nama cukai, kadar dan amaun. |
| `shipping` | object | `name`, `cost` dan `total_weight` kaedah penghantaran. |
| `bank_transfer_details` | object | Butiran bank, untuk pesanan manual transfer. |
| `amount` | string | Jumlah yang pembeli bayar. |
| `payment_channel` | string | Nama saluran, contohnya `FPX`, `DuitNow QR`, `Credit Card`, `Cash on Delivery (COD)` atau `Manual Bank Transfer`. |
| `status` | number | Kod status bayaran. `3` bermaksud berjaya dan `2` bermaksud gagal. Mungkin tiada pada `form-submit`. |
| `status_description` | string | Teks status dari Bayarcash. `payment-success` dan `payment-failed` sahaja. |
| `is_paid` | number | `1` selepas dibayar, jika tidak `0`. |
| `retry_count` | number | Berapa kali pembeli mencuba semula bayaran. |
| `created_at` | string | Masa pesanan, `DD/MM/YYYY HH:MM:SS`, waktu Malaysia. |
| `customer_id` | string | ID pelanggan BCL. |
| `classification_code` | string | Kod klasifikasi e-invois. |
| `affiliate_id`, `cookie_id` | string | Atribusi affiliate, jika ada. |
| `source_type` | string | Sama seperti `formable_type`. |
| `items` | array | Item yang dibeli. Lihat bahagian seterusnya. |

## Ruangan item

Setiap entri dalam `items` ada key berikut:

| Ruangan | Jenis | Maksud |
| --- | --- | --- |
| `id` | number | ID baris item. |
| `index`, `productable_id` | number | ID produk. `index` dikekalkan untuk integrasi lama. |
| `productable_type` | string | Contohnya `product_item`, `event_ticket` atau `booking_service`. |
| `item`, `sku`, `option` | string | Nama produk, SKU dan variasi yang dipilih. |
| `quantity` | number | Kuantiti. |
| `unit_amount`, `normal_price`, `sale_price` | string | Harga yang dibayar seunit, harga biasa dan harga jualan. |
| `amount` | string | Jumlah baris. |
| `access_url` | string atau null | Link download untuk produk digital. |
| `weight`, `total_weight` | string | Berat seunit dan untuk baris itu. |
| `metadata` | object atau null | Data tambahan item. |
| `product_image` | string | URL gambar produk, atau kosong. |
| `protected_content` | array | `payment-success` sahaja: setiap item ada `title`, `url` (link akses peribadi pembeli) dan `access_finder_url` (page tempat pembeli yang hilang link boleh mencarinya semula). Kosong pada event lain. |

## Ruangan affiliate

Objek `affiliate_data` ada `referral_id`, `affiliate_id`, `affiliate_username`, `affiliate_name`, `affiliate_email` dan `affiliate_phone`. Objek ini juga ada `commission_type` (`flat`, `percentage` atau `per_item`), `commission_rate` dan `commission_amount`. Untuk komisen per item, `commission_rate` ialah null dan `commission_breakdown` menyenaraikan setiap item. Akhir sekali, `status` (contohnya `pending` atau `approved`) dan `referral_source`.

## Perubahan daripada contoh lama

Jika anda membina integrasi anda berdasarkan contoh lama, semak perubahan ini:

- `form_id` sudah tiada. Guna `formable_id`, dan `formable_type` untuk jenis form.
- Ruangan peringkat atas baru: `formable_type`, `form_url`, `form_featured_image` dan `tracking_params`.
- Ruangan pesanan baru: `subtotal_amount`, `created_at`, `customer_id`, `is_paid`, `retry_count`, `source_type` dan ruangan diskaun, kupon, cukai dan penghantaran.
- Ruangan item baru: `id`, `productable_type`, `productable_id`, `sku`, `normal_price`, `sale_price`, `weight`, `total_weight`, `metadata`, `product_image` dan `protected_content`.
- Ruangan affiliate baru: `commission_breakdown`.
- Event baru: `payment-failed`.

## Semak sama ada webhook itu tulen

BCL tidak menandatangani webhook. Tiada signature header atau shared secret, jadi sesiapa yang tahu URL anda boleh menghantar request kepadanya. Untuk kekal selamat:

- Sebelum anda menghantar pesanan atau memberi akses, cari pesanan itu dengan `record_id` melalui [API BCL](https://bcl.my/docs/api) dan sahkan statusnya di situ. Dalam Test Mode, guna `GET /transaction/{order_number}`: `GET /transactions/{order_number}` hanya menjumpai bayaran live. Hantar header `User-Agent` anda sendiri, kerana BCL menyekat header default daripada tool seperti curl dan python-requests.
- Guna URL yang sukar diteka, contohnya dengan token rawak yang panjang dalam path.
- Pastikan sistem anda selamat menerima event yang sama lebih daripada sekali. Guna `record_id` dan `event` bersama untuk mengesan ulangan.

## Penghantaran & log

BCL menghantar setiap event sekali ke setiap URL, satu request bagi setiap URL. Sebarang respons 2xx dikira sebagai berjaya dihantar. Balas dengan cepat dan buat kerja berat selepas itu.

Setiap cubaan direkodkan di bawah **Tools** → **Webhook Logs**, dengan payload, respons anda dan sebarang ralat. BCL tidak menghantar semula secara automatik jika penghantaran gagal. Untuk menghantarnya semula:

1. Pergi ke **Tools** → **Webhook Logs**.
2. Klik menu di hujung baris, kemudian klik **Resend** dan sahkan.

   *(Screenshot: Senarai Webhook Logs dengan menu baris dibuka dan Resend ditanda)*

Klik **View Details** dalam menu yang sama untuk melihat payload yang BCL hantar dan reply endpoint anda.

## Isu biasa

### Kenapa sistem saya tidak menerima apa-apa walaupun saya sudah menambah URL webhook? Bagaimana saya mengujinya?

Buka tab Webhook Settings payment form itu dan klik Send Test. Pilih Event Type, semak Webhook URL dan hantar. Jika test itu sampai tetapi bayaran sebenar tidak, pastikan anda menandakan Payment Success, bukan hanya Form Submit. Jika test itu tidak sampai, pastikan URL anda terbuka kepada umum, bermula dengan https://, menerima POST dan membalas dengan status 2xx.

### Bagaimana sistem saya sendiri boleh tahu apabila bayaran masuk?

On-kan webhook untuk payment form itu dan tandakan Payment Success. BCL menghantar pesanan, pembeli, item dan custom field ke URL anda sebaik sahaja bayaran disahkan. Padankan dengan rekod anda menggunakan record_id, iaitu nombor pesanan BCL.

### Adakah import rekod lama akan mencetuskan webhook saya?

Hanya jika anda tandakan Run webhooks and automations pada page import. Setiap baris yang di-import kemudian mencetuskan webhook anda seolah-olah ia bayaran sebenar, jadi biarkan pilihan itu off jika anda hanya mahu rekod itu dalam BCL.
