如何通过 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 中的客户姓名和邮箱可以用于展示与对账,但不能作为关联订单的唯一依据。多个订单可能使用同一个邮箱,付款人也可能在结账时修改邮箱。
更可靠的关联流程是:
- 在本地生成一个不可猜测的订单标识。
- 通过接口支持的自定义字段随结账请求发送。
- 从 IPN 中取回该标识。
- 同时保存 Avangate 的交易编号。
- 核对商品、金额、币种和支付状态后,再更新订单。
如果当前结账接口不支持可回传的自定义字段,可以为每笔订单生成唯一关联值,或者根据 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 确认响应,都要以账户实际启用的接口版本为准。
备注:内容仅供参考。
