ASP.NET MVC 银行支付 callback 返回时出现 404 error
结论
最可能的原因是银行回调使用了 HTTP POST,而 PaymentCallback 只接受 HTTP GET。
浏览器地址栏只显示 URL,看不到请求方法。银行提交回调时,即使参数位于查询字符串中,请求方法仍可能是 POST。在地址栏按 Enter 后,浏览器会重新发送 GET 请求,因此可以正常进入 action。
为什么会返回 404
[HttpGet] 是 ASP.NET MVC 选择 action 的条件:
[HttpGet]
[AllowAnonymous]
public ActionResult PaymentCallback(string au, int order_id)
{
// ...
}
银行发送 POST 请求时,MVC 虽然能找到 PaymentCallback 对应的路由,但请求方法不符合 [HttpGet],所以不会选择这个 action。在某些 IIS 和 MVC 配置下,这会返回 404,不一定是 405。
在地址栏按 Enter 时,浏览器发送的是:
GET /Transaction/PaymentCallback/?au=70158292914&order_id=27
这个请求符合 [HttpGet],因此 action 可以正常执行。
推荐修复方式
先查看支付网关文档,确认回调使用 GET 还是 POST。如果银行规定使用 POST,应将 action 改为 [HttpPost]:
[HttpPost]
[AllowAnonymous]
public ActionResult PaymentCallback(string au, int order_id)
{
// 验证回调数据
// 向银行查询或验证交易结果
// 更新订单状态
return Content("OK");
}
ASP.NET MVC 的默认模型绑定通常既能读取查询字符串,也能读取表单字段。因此,即使请求方法是 POST,参数仍在 URL 查询字符串中,通常也能正常绑定。
如果支付网关可能发送 GET 或 POST,可以明确允许这两种方法:
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
[AllowAnonymous]
public ActionResult PaymentCallback(string au, int order_id)
{
// ...
return Content("OK");
}
也可以暂时移除 [HttpGet],确认问题是否由请求方法不匹配引起。不过在生产环境中,最好根据支付网关协议明确限制允许的方法。
如何确认实际请求方法
不要只根据浏览器地址栏判断请求方法。可以通过以下方式确认:
- 查看 IIS 日志中记录的请求方法。
- 使用浏览器开发者工具的 Network 面板。
- 查阅支付网关的技术文档或回调示例。
- 在测试环境中记录
Request.HttpMethod、查询字符串和表单字段。
例如:
string method = Request.HttpMethod;
string auFromQuery = Request.QueryString["au"];
string auFromForm = Request.Form["au"];
如果确认银行发送的确实是 GET,再检查 IIS 请求筛选、URL Rewrite、反向代理、WAF,以及系统是否根据 Referer、User-Agent 或来源 IP 拦截请求。不过,遇到“银行返回时 404,在地址栏按 Enter 后正常”的情况,应先排查 GET 和 POST 是否不一致。
支付回调的安全注意事项
支付回调通常需要使用 [AllowAnonymous],因为银行服务器或从银行页面返回的用户没有本站的登录会话。但允许匿名访问,并不意味着可以直接信任回调参数:
- 不要只根据
au或order_id将订单标记为已支付。 - 按照银行协议验证签名、交易编号、金额、商户号和订单号。
- 必要时主动调用银行的交易验证接口。
- 确保回调处理具备幂等性,避免重复通知导致重复入账。
- 不要依赖
Referer判断请求是否来自银行,因为该请求头可能缺失,也可能被伪造。 - 跨站银行 POST 通常无法携带本站生成的防伪令牌,因此不要直接为该回调添加
[ValidateAntiForgeryToken]。应改用支付网关提供的签名或验证机制。 - 按照支付网关协议返回指定的成功响应,否则银行可能重复发送通知。
如果银行同时提供“浏览器返回地址”和“服务器异步通知地址”,应分别实现。浏览器返回地址用于向用户展示支付结果,服务器异步通知地址负责可靠地确认交易并更新订单状态。
备注:内容仅供参考。
