PAYATHON 2026

支付网关重定向到 ASPX 后 Request.Form.Count 为 0

支付阿杰

结论

.aspx 页面可以接收支付网关发送的 POST 数据。Request.Form.Count 为 0,并不表示 ASPX 不支持 POST。更可能的情况是页面实际收到的是 GET 请求,或者 POST 请求在到达页面前发生了重定向。

只要支付网关将 application/x-www-form-urlencoded 或 multipart/form-data 数据直接提交到最终 URL,ASP.NET Web Forms 就能通过 Request.Form 读取。

为什么 POST 会变成 GET

通常是回调地址发生了中间重定向,例如:

  • HTTP 自动跳转到 HTTPS。
  • 非 www 域名跳转到 www 域名,或反向跳转。
  • Forms Authentication 将未登录请求重定向到登录页。
  • URL Rewrite、路由规则或反向代理触发了跳转。
  • 回调页面的代码、基类或 MasterPage 调用了 Response.Redirect。
  • 支付网关先向某个地址发送 POST,该地址处理后又跳转到当前页面。
  • 支付网关使用的是”浏览器返回地址”,而不是”服务器通知地址”。

遇到 301、302 或 303 重定向时,客户端通常会将后续请求改为 GET,POST 请求体也不会传到新地址。因此,最终页面会看到:

Request.HttpMethod == "GET"
Request.Form.Count == 0

307 和 308 理论上会保留原请求方法和请求体,但支付回调不应依赖重定向,最好直接配置最终 URL。

浏览器开发者工具中显示的 GET 也可能只是请求链的最后一步。开启 “Preserve log/保留日志”,再检查前面是否出现过 POST 请求和 301/302/303 响应。

解决步骤

1. 使用最终的 HTTPS 地址

将支付网关的回调地址直接设为最终可访问的地址,例如:

https://www.example.com/payment/callback.aspx

不要提交会触发跳转的地址:

http://example.com/payment/callback.aspx

如果服务器随后将其跳转到:

https://www.example.com/payment/callback.aspx

POST 数据很可能在跳转时已经丢失。

2. 允许支付网关匿名访问

支付回调通常不能要求登录。如果网站启用了 Forms Authentication,需要明确允许匿名访问回调页面:

<configuration>
  <location path="payment/callback.aspx">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>
</configuration>

同时检查 IIS、反向代理、WAF 和应用程序中的权限规则,确认请求不会被重定向到登录页面。

3. 区分返回地址和通知地址

很多支付平台提供两类 URL,用途并不相同:

  • Return URL:支付完成后,将用户的浏览器带回网站,通常使用 GET。
  • Notify URL、Callback URL 或 Webhook URL:支付平台的服务器发送支付结果,通常使用 POST。

支付状态应以服务器通知为准。Return URL 主要用于向用户展示”支付处理中”或”支付成功”等界面,不能作为唯一的到账依据。

不同支付网关使用的名称和请求方式可能不同,应以相应网关的接口文档为准。

ASPX 接收表单 POST 的示例

下面的代码可以检查页面实际收到的请求:

using System;
using System.Collections.Specialized;
using System.Text;
using System.Web;

public partial class PaymentCallback : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        StringBuilder output = new StringBuilder();

        output.AppendLine("Method: " + Request.HttpMethod);
        output.AppendLine("Content-Type: " + Request.ContentType);
        output.AppendLine("Form Count: " + Request.Form.Count);

        foreach (string key in Request.Form.AllKeys)
        {
            string value = Request.Form[key];

            output.AppendLine(
                HttpUtility.HtmlEncode(key) + " = " +
                HttpUtility.HtmlEncode(value)
            );
        }

        lblAllFormItems.Text = output
            .ToString()
            .Replace(Environment.NewLine, "<br />");
    }
}

这里不需要依赖 IsPostBack。支付网关提交的 POST 不是由 Web Forms 控件触发的回发,直接检查 Request.HttpMethod 和 Request.Form 即可。

在正式环境中,不要把支付参数直接显示给用户,尤其是签名、订单信息和个人数据。必要信息应写入受保护的日志,敏感字段需要脱敏。

如果收到 POST,但 Request.Form 仍为空

这时应检查 Content-Type。

Request.Form 主要解析以下表单格式:

application/x-www-form-urlencoded
multipart/form-data

如果支付网关发送的是 JSON:

application/json

就需要读取请求体,不能使用 Request.Form:

using System.IO;

protected void Page_Load(object sender, EventArgs e)
{
    string body;

    Request.InputStream.Position = 0;

    using (StreamReader reader = new StreamReader(Request.InputStream))
    {
        body = reader.ReadToEnd();
    }

    // 按支付网关规定的 JSON 格式反序列化 body
}

读取前可以记录以下诊断信息:

string method = Request.HttpMethod;
string contentType = Request.ContentType;
int contentLength = Request.TotalBytes;
string rawUrl = Request.RawUrl;

如果 Request.HttpMethod 是 POST,Request.TotalBytes 大于 0,但 Request.Form.Count 为 0,通常表示请求体不是标准表单格式。

更适合支付通知的处理方式

如果支付通知不需要页面 UI,可以改用 .ashx Handler 或 Web API Endpoint。下面是一个简单的 .ashx 示例:

using System.Web;

public class PaymentNotify : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        if (!string.Equals(
            context.Request.HttpMethod,
            "POST",
            System.StringComparison.OrdinalIgnoreCase))
        {
            context.Response.StatusCode = 405;
            return;
        }

        string orderId = context.Request.Form["order_id"];
        string amount = context.Request.Form["amount"];
        string signature = context.Request.Form["signature"];

        // 1. 按支付网关文档验证 signature
        // 2. 查询并核对订单、金额、币种和商户号
        // 3. 以幂等方式更新订单状态
        // 4. 返回支付网关要求的确认内容

        context.Response.StatusCode = 200;
        context.Response.ContentType = "text/plain";
        context.Response.Write("OK");
    }

    public bool IsReusable
    {
        get { return false; }
    }
}

注意事项

  • 不能仅凭 POST 参数将订单标记为已支付,必须验证网关签名。
  • 需要核对订单号、金额、币种和商户账号,不能只检查”支付成功”字段。
  • 支付网关可能重复发送通知,因此订单更新必须具备幂等性。
  • 回调处理应尽快返回网关要求的成功响应,耗时操作可以交给后台队列。
  • 不要在回调处理中再次重定向,也不要返回完整的支付数据。
  • 如果请求来自支付网关服务器,就不能依赖 Cookie、Session 或用户登录状态。
  • 不要只根据来源 IP 判断请求是否可信,签名验证通常才是主要的校验手段。

排查时不必修改 foreach 循环。应先找出 POST 在到达 .aspx 页面前是否发生了重定向或认证跳转。将支付网关配置为直接向最终、公开可访问且不会跳转的 URL 发送 POST 后,Request.Form 就能正常读取表单参数。

备注:内容仅供参考。