Callback URL和checksum:验证Bayarcash callback
付款完成时,Bayarcash会用callback通知你的服务器,并通过return URL把你的顾客送回来。每个callback都带有checksum:在信任这些资料之前,先用你的API Secret Key检查它。你也可以在自己的request中加上checksum,让Bayarcash知道它们没有被修改。
Callback URL和return URL
Section titled “Callback URL和return URL”你在每个payment intent上设置这两个URL(callback_url和return_url)。它们的作用不同:
| Callback URL | Return URL | |
|---|---|---|
| 由谁调用 | Bayarcash的服务器,直接调用你的服务器 | 你顾客的浏览器,由Bayarcash重定向 |
| 怎样到达 | POST | GET,field在query string中(API v3) |
| 可以用来判断订单状态吗? | 可以,在检查checksum之后 | 不可以:顾客可能关闭标签页或修改URL |
| 用途 | 把订单标为已付款或失败 | 向顾客显示结果页面 |
只根据callback更新订单。如果return URL先到达,就显示“付款确认中”的信息并等待callback,或用API查询这笔交易。
Bayarcash发送的callback
Section titled “Bayarcash发送的callback”每个callback都有一个record_type field,告诉你它是什么:
| Callback | 什么时候发送 |
|---|---|
| Pre-transaction | 在付款初期,交易记录还未确定之前。 |
| Transaction | 付款到达最终状态时(成功、失败、取消等)。 |
| FPX Direct Debit authorization | 顾客完成或放弃e-Mandate登记时。 |
| FPX Direct Debit bank approval | 顾客的银行批准或拒绝授权(mandate)时。这可能在几天后才发生。 |
| FPX Direct Debit transaction | 每次从有效的mandate扣款时。 |
交易callback中的status field是一个数字:
| 状态 | 意思 |
|---|---|
| 0 | New(新建) |
| 1 | Pending(待处理) |
| 2 | Unsuccessful(不成功) |
| 3 | Successful(成功) |
| 4 | Cancelled(已取消) |
| 5 | Abandoned(已放弃) |
每个callback都以HTTP 200回复
Section titled “每个callback都以HTTP 200回复”你的callback URL在检查并保存结果后,必须回复HTTP 200。如果它回复其他状态,或没有回复,Bayarcash会再次发送callback。所以你的handler必须可以安全地运行两次:在完成订单前,先检查你是否已经处理过那个transaction_id。
Callback以POST发送。Bayarcash插件和SDK把它们当作表单field读取(PHP中的$_POST)。如果你的框架收到的是application/json,就改为读取JSON body。
Checksum怎样运作
Section titled “Checksum怎样运作”签署request和验证callback使用同一个规则。每个调用之间只有field列表不同。要生成checksum:
- 取出属于该调用的field(见下表),值必须和发送或收到的完全一样。缺少的field用空值。
- 按field名称从A到Z排序。
- 按这个顺序把值连接起来,每个值之间加一个
|。 - 用你的API Secret Key作为key,计算这段文字的HMAC-SHA256。结果以小写十六进制(hex)表示,就是checksum。
验证callback时,把你的结果和callback带来的checksum field比较。使用固定时间比较(constant-time comparison),例如PHP的hash_equals()或Python的hmac.compare_digest()。如果两者不同,就拒绝这个callback。
request上的checksum是可选的,但建议加上。callback上一定有checksum:一定要验证它。
每个checksum中的field
Section titled “每个checksum中的field”每个调用使用自己的field:
| 调用 | Checksum中的field |
|---|---|
| 创建payment intent(request) | payment_channel, order_number, amount, payer_name, payer_email |
| FPX Direct Debit登记(request) | order_number, amount, payer_name, payer_email, payer_telephone_number, payer_id_type, payer_id, application_reason, frequency_mode |
| Pre-transaction callback | record_type, exchange_reference_number, order_number |
| Transaction callback | 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 |
| FPX Direct Debit bank approval callback | 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 |
| FPX Direct Debit transaction callback | record_type, batch_number, mandate_id, mandate_reference_number, transaction_id, datetime, reference_number, amount, status, status_description, cycle |
表格只列出field,顺序由第2步的排序决定。如果一个payment intent有多个payment_channel,先用逗号把渠道编号连接起来(1,5)再加入,和PHP SDK的做法一样。FPX Direct Debit的修改request和authorization callback,请使用官方SDK中的验证方法,它们带有准确的field列表。
示例:用PHP验证交易callback
Section titled “示例:用PHP验证交易callback”这段代码不使用SDK来检查交易callback。使用PHP SDK时,verifyTransactionCallbackData()一行就能做到同样的事。
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);示例:用Python签署payment intent
Section titled “示例:用Python签署payment intent”同一个规则的Python版本,用于payment intent request:
import hashlibimport hmac
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")把结果作为checksum,和其他field一起发送。完整的field参考在API参考文档的checksum指南。
- 你的callback URL和return URL要使用HTTPS。callback带有付款资料,HTTPS能在传输过程中保护这些资料。
- API Secret Key只能保存在你的服务器上。任何拿到它的人,都能伪造一个通过checksum检查的callback。
- 如果你的框架有package,就使用它。Laravel和Django package会加入一个callback route,自动检查checksum,并以HTTP 403拒绝无效的callback。
常见问题
我的WooCommerce日志显示“Invalid checksum for order”。哪里出错了?
checksum不匹配。最常见的原因是API Secret Key错误,或它来自另一个环境(在正式环境的callback上用了sandbox key,或反过来)。从你正在使用的商家后台重新复制key。
即使key正确,我的checksum还是一直不匹配。应该检查什么?
使用该调用的准确field列表,按field名称排序,并完全按照发送或收到的值使用。10.00和10会得出不同的checksum。缺少的field算作空值,不是跳过不算。
为什么同一个callback到达了两次?
当你的服务器没有以HTTP 200回复时,Bayarcash会再次发送callback。保存结果后就回复200,并检查交易ID,以免同一笔付款被处理两次。

