跳到内容

热门搜索

查看页面以Markdown格式查看

向AI询问本页内容

ChatGPTClaudePerplexityGoogle AI ModeMicrosoft CopilotGrokMistral Le Chat

Callback URL和checksum:验证Bayarcash callback

更新于

付款完成时,Bayarcash会用callback通知你的服务器,并通过return URL把你的顾客送回来。每个callback都带有checksum:在信任这些资料之前,先用你的API Secret Key检查它。你也可以在自己的request中加上checksum,让Bayarcash知道它们没有被修改。

你在每个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查询这笔交易。

每个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 URL在检查并保存结果后,必须回复HTTP 200。如果它回复其他状态,或没有回复,Bayarcash会再次发送callback。所以你的handler必须可以安全地运行两次:在完成订单前,先检查你是否已经处理过那个transaction_id。

Callback以POST发送。Bayarcash插件和SDK把它们当作表单field读取(PHP中的$_POST)。如果你的框架收到的是application/json,就改为读取JSON body。

签署request和验证callback使用同一个规则。每个调用之间只有field列表不同。要生成checksum:

  1. 取出属于该调用的field(见下表),值必须和发送或收到的完全一样。缺少的field用空值。
  2. 按field名称从A到Z排序。
  3. 按这个顺序把值连接起来,每个值之间加一个|。
  4. 用你的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:一定要验证它。

每个调用使用自己的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列表。

这段代码不使用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 request:

import hashlib
import 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,以免同一笔付款被处理两次。

这篇文章对你有帮助吗?

Cookie settings

We use Google Analytics to see which guides help and where readers get stuck. It is on by default, and you can turn it off. This device keeps your choice.