PAYATHON 2026

如何通过 Avangate IPN 按顺序发送额外参数

支付阿杰

结论

付款结果、付款人信息和 Avangate 订单号,都应从服务器端的 IPN(Instant Payment Notification)回调中获取,不能依赖付款完成后的浏览器跳转。

如果要把本地订单号传给 Avangate,并在 IPN 中取回,请使用 Avangate 后台或当前接口文档支持的自定义字段。URL 查询参数的排列顺序并不可靠,任意追加的参数也不一定会原样出现在 IPN 中。

推荐的处理流程

生成付款链接前,先在本地数据库中创建订单:

local_order_id = ORD-20260901-10001
status = pending

再将订单标识写入 Avangate 支持的自定义字段。字段名称取决于 Avangate/2Checkout 的产品版本和结账方式,可能需要在后台配置,也可能是接口明确支持的订单参数。请以当前商户后台和对应的 API 文档为准。

示意链接:

https://secure.avangate.com/order/checkout.php?ORDERSTYLE=nLW0oJXfq34%3D&PRODS=xxx3299&QTY=1&CUSTOM_FIELD=ORD-20260901-10001

其中的 CUSTOM_FIELD 只是占位名称,并非 Avangate 的固定参数。使用前要确认实际字段名,以及该字段是否会通过 IPN 回传。

配置 IPN 回调

在 Avangate 商户后台配置一个可通过公网访问的 HTTPS 地址,例如:

https://example.com/payment/avangate/ipn

支付状态变化后,Avangate 会向这个地址发送服务器端请求。IPN 数据通常包括:

  • Avangate 订单号或交易编号,例如旧版接口中常见的 REFNO
  • 订单状态
  • 商品编号、数量和金额
  • 客户姓名、邮箱和账单资料
  • 通知时间
  • 用于验证通知真实性的签名或哈希值
  • 已配置且允许回传的自定义字段

字段名称会随 Avangate/2Checkout 接口版本变化,不要照搬旧示例进行硬编码。

PHP 接收示例

下面的代码展示了基本处理结构。签名验证必须按照当前接口文档实现,不能只比较请求中的某个普通字段。

<?php

$payload = $_POST;

if (!$payload) {
    http_response_code(400);
    exit('Empty IPN payload');
}

// 必须使用 Avangate/2Checkout 当前文档规定的算法验证签名。
// verifyAvangateSignature() 需要根据实际接口版本实现。
if (!verifyAvangateSignature($payload)) {
    http_response_code(403);
    exit('Invalid signature');
}

$referenceNumber = $payload['REFNO'] ?? null;
$orderStatus     = $payload['ORDERSTATUS'] ?? null;
$customerEmail   = $payload['CUSTOMEREMAIL'] ?? null;

// 替换成实际配置并确认会由 IPN 回传的自定义字段。
$localOrderId = $payload['CUSTOM_FIELD'] ?? null;

if ($referenceNumber === null || $orderStatus === null) {
    http_response_code(400);
    exit('Missing required fields');
}

$order = findOrderByLocalId($localOrderId);

if ($order === null) {
    http_response_code(404);
    exit('Order not found');
}

// 除状态外,还应核对商品、金额和币种。
// isSuccessfulStatus() 中的成功状态值应来自当前接口文档。
if (
    isSuccessfulStatus($orderStatus) &&
    amountsMatch($order, $payload) &&
    productsMatch($order, $payload)
) {
    // 使用交易编号保证幂等,防止重复 IPN 导致重复发货。
    markOrderAsPaidOnce(
        $order['id'],
        $referenceNumber,
        $customerEmail
    );
}

// 某些旧版 IPN 要求返回包含时间和 HASH 的特定响应,
// 不能简单返回任意文本。这里应生成当前接口要求的确认内容。
echo buildAvangateIpnResponse($payload);

如何确定付款人

IPN 中的客户姓名和邮箱可以用于展示与对账,但不能作为关联订单的唯一依据。多个订单可能使用同一个邮箱,付款人也可能在结账时修改邮箱。

更可靠的关联流程是:

  1. 在本地生成一个不可猜测的订单标识。
  2. 通过接口支持的自定义字段随结账请求发送。
  3. 从 IPN 中取回该标识。
  4. 同时保存 Avangate 的交易编号。
  5. 核对商品、金额、币种和支付状态后,再更新订单。

如果当前结账接口不支持可回传的自定义字段,可以为每笔订单生成唯一关联值,或者根据 Avangate 返回的交易编号建立映射。不要只凭客户邮箱判断订单归属。

参数“顺序”并不可靠

URL 查询参数通常没有顺序要求:

?PRODS=xxx3299&QTY=1&ORDERSTYLE=...

一般与下面的写法等价:

?ORDERSTYLE=...&QTY=1&PRODS=xxx3299

数组型参数需要单独注意。一次购买多个商品时,商品编号和数量可能通过索引建立对应关系。具体格式必须遵循当前接口文档,不能根据重复出现的普通参数自行推断。

同样,在链接中随意追加:

&my_order_id=123

并不表示 Avangate 会保存这个参数或通过 IPN 将其返回。只有接口明确支持,或者已在商户后台配置的字段才可以依赖。

安全和业务注意事项

  • 不要根据付款成功跳转页直接把订单标记为已支付。用户可能关闭页面,也可能伪造访问请求。
  • 必须验证 IPN 的签名或哈希,并在服务器端使用商户密钥完成计算。密钥不能写入前端页面或付款链接。
  • 除了状态字段,还要核对金额、币种、商品编号和数量。
  • IPN 可能重复发送,订单更新和发货逻辑必须保证幂等。
  • 不要只处理“付款成功”状态。退款、拒付、撤销和订阅续费等状态,也应按业务需要更新。
  • 先保存原始 IPN 数据和处理结果,再返回接口要求的确认响应,以便排查漏单。
  • 使用测试环境或测试订单验证完整流程,但不要直接把测试状态值用于生产环境。
  • Avangate 后来归入 2Checkout,旧版 Avangate 参数可能与新版 2Checkout 接口不同。签名算法、成功状态名称、自定义字段和 IPN 确认响应,都要以账户实际启用的接口版本为准。

备注:内容仅供参考。