API 参考

English

异步通知

支付、退款、代付状态变化时,HansaPay 向 notify_url 发送的签名通知。

HansaPay 系统会把支付、退款、代付结果 POST 到原请求中传入的 notify_url。

拒付、链接过期仍未支付的收银台订单,以及支付变为 REFUNDED 时均不发送通知(此时会针对该退款发送 refund.updated)。

通知请求头

HansaPay 系统通知时会带:

  • X-MerchantID
  • X-Timestamp
  • X-Nonce
  • X-Sign

验签规则与请求签名完全一致:

Text
merchantID + "\n" + timestamp + "\n" + nonce + "\n" + rawBody

商户侧建议处理流程:

  1. 原样读取 HTTP body
  2. 取请求头 X-MerchantID、X-Timestamp、X-Nonce、X-Sign
  3. 用商户密钥重新计算签名
  4. 比对签名是否一致
  5. 验签成功后再做业务处理
  6. 返回纯文本 success

Java 验签示例

Java
String merchantId = request.getHeader("X-MerchantID");
String timestamp = request.getHeader("X-Timestamp");
String nonce = request.getHeader("X-Nonce");
String receivedSign = request.getHeader("X-Sign");
String rawBody = requestBodyAsString;
String secret = "your_merchant_secret";

String payload = merchantId + "\n" + timestamp + "\n" + nonce + "\n" + rawBody;
String expectedSign = hmacSha256Hex(payload, secret);

if (!expectedSign.equalsIgnoreCase(receivedSign)) {
    throw new RuntimeException("invalid signature");
}

PHP 验签示例

PHP
<?php

$merchantId = $_SERVER['HTTP_X_MERCHANTID'];
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'];
$nonce = $_SERVER['HTTP_X_NONCE'];
$receivedSign = $_SERVER['HTTP_X_SIGN'];
$rawBody = file_get_contents('php://input');
$secret = 'your_merchant_secret';

$payload = $merchantId . "\n" . $timestamp . "\n" . $nonce . "\n" . $rawBody;
$expectedSign = hash_hmac('sha256', $payload, $secret);

if (strtolower($expectedSign) !== strtolower($receivedSign)) {
    throw new Exception('invalid signature');
}

Go 验签示例

Go
payload := merchantID + "\n" + timestamp + "\n" + nonce + "\n" + rawBody
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
expected := hex.EncodeToString(mac.Sum(nil))
if !strings.EqualFold(expected, receivedSign) {
	return errors.New("invalid signature")
}

通知体结构

HansaPay 系统对支付、退款、代付事件使用统一的通知体格式。

通知字段

字段类型说明
eventstringpayment.updated / refund.updated / payout.updated
order_nostringHansaPay 系统订单号
merchant_order_nostring商户订单号
payment_methodstring支付方式
trans_amountobject支付与退款:实收或实退的美元金额,其他币种订单与 API 响应不同。代付:代付金额
requested_amountobject商户原始请求金额;仅当金额被向下取整到上游支持的价位时返回(钱包,以及部分通道的卡),此时 trans_amount 为实际收取金额
trade_infoobject支付:商品信息。退款:goods_name 为退款原因
statusstring当前状态
status_reasonstring仅代付失败或取消时返回:Payout request failed、Payout failed 或 Payout canceled
metadatastring商户透传参数
original_order_nostring原 HansaPay 系统订单号,退款时填写
original_merchant_order_nostring原商户订单号,退款时填写
finished_atstring完成时间。退款与失败的支付不返回
created_atstring创建时间

支付失败通知不包含失败原因。

支付通知示例

JSON
{
  "event": "payment.updated",
  "order_no": "O202406240001",
  "merchant_order_no": "M202406240001",
  "payment_method": "CARD",
  "trans_amount": {
    "currency": "USD",
    "value": "99.99"
  },
  "trade_info": {
    "goods_name": "VIP Membership",
    "description": "Monthly subscription"
  },
  "status": "SUCCESS",
  "metadata": "biz=member&uid=10001",
  "finished_at": "2026-06-24T10:01:23+08:00",
  "created_at": "2026-06-24T10:00:00+08:00"
}

退款通知示例

JSON
{
  "event": "refund.updated",
  "order_no": "R202406240001",
  "merchant_order_no": "MR202406240001",
  "payment_method": "CARD",
  "trans_amount": {
    "currency": "USD",
    "value": "99.99"
  },
  "trade_info": {
    "goods_name": "customer requested"
  },
  "status": "SUCCESS",
  "metadata": "refund=manual",
  "original_order_no": "O202406240001",
  "original_merchant_order_no": "M202406240001",
  "created_at": "2026-06-24T11:00:00+08:00"
}

代付通知示例

JSON
{
  "event": "payout.updated",
  "order_no": "P202406240001",
  "merchant_order_no": "MP202406240001",
  "payment_method": "CASH_APP",
  "trans_amount": {
    "currency": "USD",
    "value": "88.50"
  },
  "status": "SUCCESS",
  "metadata": "batch=20260624",
  "finished_at": "2026-06-24T12:00:08+08:00",
  "created_at": "2026-06-24T12:00:00+08:00"
}

商户回调响应要求

商户处理通知成功后,必须返回:

Text
success

要求:

  • 纯文本
  • 不带 JSON 包裹
  • 不带引号
  • 小写 success
  • 5 秒内应答;耗时处理放在应答之后
  • HansaPay 只检查 body;其他任何 body 均视为失败,即使 HTTP 状态为 200
  • 未被确认的通知会重新发送,约 90 分钟内共最多 9 次。每次重发都会使用新的时间戳和 nonce 重新签名
  • 同一通知可能到达多次,且可能乱序到达。body 反映发送时的状态。通知不带事件 ID:请按 event + order_no + status 去重