# 直接扣账（Direct Debit）webhook payload

> Direct Debit申请、授权批准和每次每月扣款时，BCL发送到你webhook URL的JSON，逐个栏位说明。
>
> Source: https://docs.bcl.my/zh/webhook-direct-debit/

Direct Debit表单会在三个时候发送webhook：顾客申请时、银行批准或拒绝授权（mandate）时，以及之后的每一次扣款。每次都是一个HTTP `POST`，JSON body包含`event`和`data`。本页列出每个栏位。

完整的BCL API，请看[API参考文档](https://bcl.my/docs/api)。

## 设置你的webhook URL

Webhook是在Direct Debit表单本身设置的：

1. 前往**Payments** → **Forms**，打开你的Direct Debit表单。
2. 点击顶部的**Advanced**，然后打开**Webhook Settings**标签页。
3. 开启**Enable Webhook**，输入一个或多个**Webhook URLs**（最多10个，每个都以`https://`开头）。如果没有显示输入框，先点击**Add Webhook URL**。
4. 在**Webhook Events**下，勾选你需要的事件。请看下一节。

   *(Screenshot: Webhook Events列表，四个事件都已勾选，Direct Debit已标示)*

5. 点击**Save Advanced Settings**。

要测试你的endpoint，点击**Send Test**，并在**Event Type**选择**Direct Debit - When direct debit transactions occur**。BCL会发送三个范例请求，每个阶段一个：申请、批准和扣款。

*(Screenshot: Send Test Webhook面板，Direct Debit事件类型已标示，预览里有三个范例payload)*

## 哪个事件在什么时候触发

只勾选**Direct Debit**复选框并不包括申请。四个事件和授权的对应如下：

| 阶段 | `event` | 勾选这个事件 | `record_type` |
| --- | --- | --- | --- |
| 顾客提交表单 | `form-submit` | **Form Submit** | `DirectDebit` |
| 银行批准登记或变更 | `payment-success` | **Payment Success** | `DirectDebit` |
| 银行拒绝，或失败 | `payment-failed` | **Payment Failed** | `DirectDebit` |
| 每一次扣款 | `direct-debit` | **Direct Debit** | `DirectDebitDeduction` |

授权终止时，BCL不会发送webhook。

## 申请和批准payload

`form-submit`、`payment-success`和`payment-failed`事件使用同一个结构。这是一个已批准登记的`payment-success`。数值都是虚构的。

```json
{
  "event": "payment-success",
  "data": {
    "formable_type": "payment_form",
    "formable_id": 34,
    "form_title": "Monthly Fees",
    "form_url": "https://shop.example.com/form/monthly-fees",
    "form_featured_image": "",
    "record_type": "DirectDebit",
    "record_id": "LINK-00041",
    "main_data": {
      "id": "01jfmnrycszgmdhdk78nmtr3gs",
      "payer_name": "Ali bin Abu",
      "payer_email": "ali@example.com",
      "payer_telephone_number": "+60123456789",
      "order_number": "LINK-00041",
      "transaction_type": "live",
      "currency": "MYR",
      "subtotal_amount": "10.00",
      "fee_amount": "1.50",
      "amount": "11.50",
      "application_type": "Enrolment",
      "approval_status": "Approved",
      "bayarcash_mandate_id": "md_AbC123",
      "mandate_reference_number": "E-20261727400000",
      "effective_date": "2026-10-01",
      "frequency_mode": "MT",
      "created_at": "27/09/2026 10:15:00"
    },
    "tracking_params": null,
    "custom_fields_data": {
      "company_name": "Contoh Trading"
    }
  }
}
```

`data`对象有这些键：

| 栏位 | 类型 | 意思 |
| --- | --- | --- |
| `formable_type` | string | 永远是`payment_form`。取代旧的`form_id`。 |
| `formable_id` | number | 表单ID。 |
| `form_title` | string | 表单标题。 |
| `form_url` | string | 表单的公开链接，或空值。 |
| `form_featured_image` | string | 封面图片URL，或空值。 |
| `record_type` | string | `DirectDebit`。 |
| `record_id` | string | 该授权的BCL订单号码。每一次扣款都保持不变。 |
| `main_data` | object | 授权。请看下面。 |
| `tracking_params` | null | Direct Debit不记录。 |
| `custom_fields_data` | object | 表单的自定义栏位，以栏位名称为键。如果没有，就不会出现。 |

Direct Debit没有`receipt_url`。

在`main_data`里，BCL会省略空的栏位：

| 栏位 | 类型 | 意思 |
| --- | --- | --- |
| `id` | string | 内部授权ID。 |
| `payer_name`, `payer_email`, `payer_telephone_number` | string | 顾客的资料。 |
| `order_number` | string | 与`record_id`相同。 |
| `transaction_type` | string | `live`或`test`。 |
| `currency` | string | 通常是`MYR`。 |
| `subtotal_amount`, `fee_amount` | string | 未加手续费的金额，以及手续费。一定会发送。 |
| `amount` | string | 每期扣除的金额。 |
| `discount_amount`, `coupon_code`, `coupon_usage_id`, `additional_fee_amount`, `tax_amount`, `shipping`, `classification_code` | various | 只有表单使用时才会发送。意思与[付款表单payload](/zh/webhook-payment-form/)相同。 |
| `application_type` | string | 仅限批准事件：`Enrolment`或`Maintenance`（授权的变更）。 |
| `approval_status` | string | 批准事件时，是`New`、`Waiting Approval`、`Failed Bank Verification`、`Active`、`Terminated`、`Approved`、`Rejected`、`Cancelled`或`Error`其中之一。在`form-submit`则是原始状态码，或不会出现。 |
| `bayarcash_mandate_id` | string | 银行回复后的Bayarcash授权ID。 |
| `mandate_reference_number` | string | 来自银行的授权参考号码。 |
| `effective_date`, `expiry_date` | string | 授权的开始和结束日期。 |
| `frequency_mode` | string | 扣款频率。`MT`表示每月。 |
| `created_at` | string | 申请时间，`DD/MM/YYYY HH:MM:SS`，马来西亚时间。 |
| `items` | array | 表单上的产品（如果有）：`index`、`item`、`option`、`quantity`、`unit_amount`、`amount`、`access_url`、`product_image`和`protected_content`。 |

## 扣款payload

Bayarcash回报的每一个扣款结果，不论成功与否，BCL都会发送`direct-debit`。这个payload仍然使用`form_id`。

```json
{
  "event": "direct-debit",
  "data": {
    "form_id": 34,
    "form_title": "Monthly Fees",
    "record_type": "DirectDebitDeduction",
    "record_id": "LINK-00041",
    "main_data": {
      "order_number": "1-727-400-448-141498",
      "payer_name": "Ali bin Abu",
      "payer_email": "ali@example.com",
      "payer_telephone_number": "+60123456789",
      "mandate_id": "md_AbC123",
      "mandate_reference_number": "E-20261727400000",
      "bayarcash_transaction_id": "trx_3qngpY",
      "batch_number": "HLB1727400000",
      "amount": "11.50",
      "status": 3,
      "status_description": "Successful",
      "datetime": "2026-10-01 00:00:00",
      "cycle": 1
    },
    "custom_fields_data": {
      "company_name": "Contoh Trading"
    }
  }
}
```

扣款栏位的意思：

| 栏位 | 类型 | 意思 |
| --- | --- | --- |
| `form_id` | number | 表单ID。 |
| `form_title` | string | 表单标题。 |
| `record_type` | string | `DirectDebitDeduction`。 |
| `record_id` | string | 该授权的BCL订单号码。 |
| `main_data.order_number` | string | 这次扣款的参考号码。 |
| `payer_name`, `payer_email`, `payer_telephone_number` | string | 授权里的顾客资料。 |
| `mandate_id` | string | Bayarcash授权ID。 |
| `mandate_reference_number` | string | 来自Bayarcash的授权参考号码。 |
| `bayarcash_transaction_id` | string | 这次扣款的Bayarcash交易ID。 |
| `batch_number` | string或null | 银行批次号码。 |
| `amount` | string | 扣除的金额。 |
| `status` | number | 扣款状态码。`3`表示成功。 |
| `status_description` | string或null | 来自Bayarcash的状态文字。 |
| `datetime` | string | 扣款日期和时间。 |
| `cycle` | number或null | 这是第几次扣款，从1开始算。 |
| `custom_fields_data` | object | 表单的自定义栏位。如果没有，就不会出现。 |

## 与旧范例的不同

如果你是根据旧范例创建整合的，请检查这些变更：

- 申请和批准payload不再有`form_id`。使用`formable_id`。
- 申请和批准的新栏位：`formable_type`、`form_url`、`form_featured_image`、`tracking_params`、`subtotal_amount`、`created_at`，以及表单有产品时的`items`。
- 被拒绝或失败的批准现在以`payment-failed`发送。
- 扣款payload没有改变。

## 确认webhook是真的

BCL不会为webhook签名，所以没有signature可以检查。在根据webhook采取行动之前，先通过[BCL API](https://bcl.my/docs/api)查找授权或订单，使用难以猜到的URL，并确保同一个事件处理两次也不会出问题。每次尝试都列在**Tools** → **Webhook Logs**。BCL不会自动重试失败的发送：打开该行的菜单，点击**Resend**。在测试模式下，用`GET /transaction/{order_number}`查找订单，并发送你自己的`User-Agent` header。

## 常见问题

### 我勾选了Direct Debit，但顾客申请时我没有收到webhook。为什么？

Direct Debit事件只包括扣款。申请使用Form Submit，银行批准或拒绝则使用Payment Success或Payment Failed。四个都勾选，就能从头到尾追踪一个授权（mandate）。

### 怎样把每月扣款对应回顾客的授权？

在direct-debit payload里，record_id是该授权的BCL订单号码，与你在申请时收到的record_id相同。main_data.mandate_id是Bayarcash授权ID，与批准webhook里的bayarcash_mandate_id相同。

### 为什么测试webhook和真实的不一样？

Direct Debit的Send Test范例在申请和批准payload里仍然使用form_id。真实的申请和批准webhook则改为发送formable_type和formable_id。扣款webhook不论测试或真实，都使用form_id。
