ProtectPay Hosted Payment Page 支付处理超时如何解决?
结论
外部 Hosted Payment Page 显示 timed out 时,修改 MVC 前端通常解决不了问题。应优先检查 ProtectPay 提交支付后访问的回调地址、返回地址或通知接口,确认这些地址可以从公网访问,并且能快速返回成功响应。
如果支付请求已经进入 ProtectPay,但页面无法跳回网站,常见原因有:
- 回调 URL 只能从本机或内网访问;
- HTTPS 证书、TLS 协议或域名解析有误;
- 回调接口要求登录,因此返回
302; - 防火墙、WAF、代理或 IP 白名单拦截了 ProtectPay;
- MVC 防伪验证拒绝了第三方 POST;
- 回调处理时间过长,超过 ProtectPay 的等待时间;
- 创建 hosted transaction identifier 时传入的 URL 无效或编码有误;
- ProtectPay 或其上游支付处理服务发生超时。
具体要使用哪个 URL 字段、超时时间是多少,应以当前 ProtectPay 接口文档和商户配置为准。仅凭浏览器页面上的提示无法确定原因。
先确认支付是否实际成功
“没有跳回网站”不一定表示”支付失败”。Hosted Payment Page 超时后,交易仍有可能已经获得授权。
先在 ProtectPay 商户后台或交易查询接口中,通过以下信息查询交易状态:
- hosted transaction identifier;
- merchant transaction ID;
- ProtectPay transaction ID;
- 支付发生时间和金额。
确认状态之前,不要让用户直接重新付款,以免造成重复扣款。业务系统应使用唯一订单号,并确保支付结果处理具有幂等性。
检查回调地址是否可访问
回调地址必须是 ProtectPay 服务器能够访问的公网 HTTPS 地址,例如:
https://payments.example.com/payment/protectpay/callback
以下地址通常不能作为生产环境的回调地址:
http://localhost:5000/payment/callback
http://127.0.0.1/payment/callback
http://192.168.1.20/payment/callback
同时检查以下事项:
- 域名能否通过公共 DNS 正确解析;
- HTTPS 证书是否有效,证书链是否完整;
- 接口是否依赖浏览器 Cookie 或用户 Session;
- 接口是否会跳转到登录页;
- 接口是否允许 ProtectPay 使用的 HTTP 方法;
- 反向代理是否把请求转发到了正确的应用;
- 防火墙或 WAF 是否拦截了请求;
- URL 中的查询参数是否正确编码。
可以从网站所在网络之外测试接口:
curl -i -X POST "https://payments.example.com/payment/protectpay/callback" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "test=1"
这个测试只能说明普通公网客户端可以访问该接口,不能完全证明 ProtectPay 服务器也能访问。最终仍要结合服务器访问日志判断。
让回调接口尽快返回
ProtectPay 等待响应期间,不要执行发送邮件、生成报表或调用多个下游系统等耗时操作。回调接口只需完成必要流程:
- 接收并解析请求;
- 验证来源和签名;
- 根据交易 ID 判断是否已经处理;
- 保存原始通知和交易状态;
- 立即返回 ProtectPay 要求的成功响应;
- 将后续业务交给后台队列处理。
下面是一个简化的 ASP.NET MVC 示例。字段名和响应内容仅用于展示结构,必须替换成 ProtectPay 文档规定的实际格式:
[AllowAnonymous]
[HttpPost]
public ActionResult ProtectPayCallback()
{
string transactionId = Request.Form["transactionId"];
string status = Request.Form["status"];
string signature = Request.Form["signature"];
if (string.IsNullOrWhiteSpace(transactionId))
{
return new HttpStatusCodeResult(400);
}
if (!VerifyProtectPaySignature(Request.Form, signature))
{
return new HttpStatusCodeResult(401);
}
// 必须按 transactionId 或 merchant transaction ID 做幂等更新。
SavePaymentNotification(transactionId, status, Request.Form);
// 耗时操作应进入后台队列。
QueuePaymentProcessing(transactionId);
return new HttpStatusCodeResult(200);
}
如果回调使用 JSON,可以采用类似结构:
[AllowAnonymous]
[HttpPost]
public ActionResult ProtectPayCallback(ProtectPayNotification notification)
{
if (notification == null ||
string.IsNullOrWhiteSpace(notification.TransactionId))
{
return new HttpStatusCodeResult(400);
}
if (!VerifyProtectPayNotification(notification, Request))
{
return new HttpStatusCodeResult(401);
}
SavePaymentNotification(notification);
QueuePaymentProcessing(notification.TransactionId);
return new HttpStatusCodeResult(200);
}
不要直接照搬示例中的字段名或签名算法。不同的 ProtectPay 接口或商户配置可能使用不同的通知格式。
排查 MVC 常见拦截问题
第三方服务器发送的 POST 通常不带网站生成的防伪 Token。如果回调 Action 使用了:
[ValidateAntiForgeryToken]
请求可能还没进入业务代码就被拒绝。第三方回调一般需要单独豁免防伪 Token,但仍须使用 ProtectPay 提供的签名、共享密钥或交易查询机制验证请求,不能直接信任提交的内容。
还要检查回调 Action 是否使用了:
[Authorize]
如果访问回调需要用户登录,ProtectPay 可能会收到 302,随后被重定向到登录页。服务端通知接口应允许匿名请求到达,再通过支付平台提供的安全机制完成认证。
查看日志定位超时发生在哪一段
生成 hosted transaction identifier 时,应记录:
- 本地订单号;
- hosted transaction identifier;
- 配置的返回 URL 和通知 URL;
- 请求时间;
- ProtectPay 返回的关联 ID。
回调入口应记录:
- 收到请求的时间;
- HTTP 方法和路径;
- HTTP 状态码;
- 处理耗时;
- ProtectPay 提供的交易 ID;
- 验签结果。
不要记录完整卡号、CVV、磁道数据、密钥或完整的敏感请求体。
可以根据日志做初步判断:
- 完全没有回调日志:检查 URL、DNS、证书、防火墙和 ProtectPay 配置;
- 收到请求但返回
401或403:检查身份验证、验签和 WAF; - 返回
302:检查登录跳转、HTTPS 跳转和 URL 规范化; - 返回
404:检查 MVC 路由和部署路径; - 返回
500:查看应用异常日志; - 请求耗时过长:减少同步处理,将耗时任务放入后台队列;
- 回调成功但浏览器没有返回:检查 return URL,并确认返回页面是否依赖已经过期的 Session。
无法从本地解决时
如果服务器日志表明 ProtectPay 从未访问回调地址,或者回调已经快速返回成功,但 Hosted Payment Page 仍然提示超时,应向 ProtectPay 支持团队提供以下信息:
- hosted transaction identifier;
- merchant transaction ID;
- ProtectPay transaction ID;
- 准确时间和时区;
- 测试或生产环境;
- 返回 URL 和通知 URL 的域名;
- 服务端访问日志中的状态码和耗时;
- 页面错误截图或错误代码。
不要发送完整信用卡号、CVV、API 密钥或其他 PCI 敏感数据。支付平台支持团队可以通过关联 ID 判断超时发生在 Hosted Payment Page、回调请求还是上游支付处理器。
备注:内容仅供参考。
