PAYATHON 2026

C# ASP.NET 如何获取 POST method 返回的数据?

支付小周

结论

支付网关通过普通表单 POST 回调时,可以在回调 URL 对应的 ASP.NET 页面中通过 Request.Form 读取字段:

string orderId = Request.Form["order_id"];
string status = Request.Form["status"];

"order_id" 和 "status" 必须与支付网关文档中的字段名完全一致。收到这些字段并不代表付款一定成功。更新订单前,还要按照网关文档验证签名,并核对订单号、金额和交易状态。

ASP.NET Web Forms 获取 POST 字段

假设回调地址为:

https://example.com/payment/return.aspx

可以在 return.aspx.cs 的 Page_Load 中读取字段:

using System;

public partial class PaymentReturn : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string orderId = Request.Form["order_id"];
        string transactionId = Request.Form["transaction_id"];
        string paymentStatus = Request.Form["status"];
        string amount = Request.Form["amount"];

        if (string.IsNullOrEmpty(orderId))
        {
            Response.StatusCode = 400;
            Response.Write("Missing order_id");
            return;
        }

        // 验证签名、订单号、金额和支付状态后,再更新本地订单。
    }
}

如果需要确认网关实际发送了哪些表单字段,可以遍历 Request.Form:

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

    // 仅用于调试。生产环境不要记录卡号、密钥等敏感数据。
    System.Diagnostics.Debug.WriteLine($"{key} = {value}");
}

读取字段时,最好明确使用 Request.Form["字段名"]。Request["字段名"] 虽然也可能返回结果,但它会同时查询表单、查询字符串和 Cookie 等多个集合。如果存在同名字段,结果可能产生歧义。

ASP.NET MVC 获取方式

在传统 ASP.NET MVC 控制器中,可以直接读取 Request.Form:

[HttpPost]
public ActionResult PaymentReturn()
{
    string orderId = Request.Form["order_id"];
    string status = Request.Form["status"];
    string amount = Request.Form["amount"];

    // 验证回调并更新订单。

    return Content("OK");
}

也可以使用模型绑定:

public class PaymentCallback
{
    public string order_id { get; set; }
    public string transaction_id { get; set; }
    public string status { get; set; }
    public decimal amount { get; set; }
}
[HttpPost]
public ActionResult PaymentReturn(PaymentCallback callback)
{
    if (callback == null || string.IsNullOrWhiteSpace(callback.order_id))
    {
        return new HttpStatusCodeResult(400);
    }

    // 验证签名和交易数据。

    return Content("OK");
}

模型属性名需要与网关提交的字段名匹配。名称不一致时,需要通过相应的绑定配置建立映射。

ASP.NET Core 获取方式

ASP.NET Core 项目可以通过模型绑定接收表单:

public class PaymentCallback
{
    public string OrderId { get; set; }
    public string TransactionId { get; set; }
    public string Status { get; set; }
    public decimal Amount { get; set; }
}
[HttpPost]
public IActionResult PaymentReturn([FromForm] PaymentCallback callback)
{
    if (string.IsNullOrWhiteSpace(callback.OrderId))
    {
        return BadRequest("Missing OrderId");
    }

    // 验证签名和交易数据。

    return Ok("OK");
}

如果网关字段采用下划线命名,可以显式指定字段名:

using Microsoft.AspNetCore.Mvc;

public class PaymentCallback
{
    [FromForm(Name = "order_id")]
    public string OrderId { get; set; }

    [FromForm(Name = "transaction_id")]
    public string TransactionId { get; set; }

    [FromForm(Name = "status")]
    public string Status { get; set; }
}

还可以直接读取表单集合:

[HttpPost]
public async Task<IActionResult> PaymentReturn()
{
    IFormCollection form = await Request.ReadFormAsync();

    string orderId = form["order_id"];
    string status = form["status"];

    return Ok("OK");
}

先确认 POST 数据的格式

Request.Form 适用于以下常见内容类型:

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

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

{
  "order_id": "A10001",
  "status": "paid"
}

则不能按普通表单读取。在 ASP.NET Core 中应使用 [FromBody]:

[HttpPost]
public IActionResult PaymentReturn([FromBody] PaymentCallback callback)
{
    if (callback == null)
    {
        return BadRequest();
    }

    return Ok("OK");
}

究竟使用 Request.Form 还是 JSON 反序列化,要以支付网关文档声明的 Content-Type 和请求示例为准。

支付回调的注意事项

验证签名

POST 请求可以被伪造。必须使用网关提供的签名算法或 SDK 验证签名,不能只检查 status == "paid"。

核对交易数据

签名验证通过后,还要核对:

  • 订单号是否存在;
  • 金额和币种是否与本地订单一致;
  • 交易是否属于当前商户;
  • 支付状态是否确实表示成功;
  • 交易编号是否已经处理过。

安全处理重复回调

支付网关可能因为超时或没有收到正确响应而重复发送通知。更新订单时需要保证幂等性。例如,可以为网关交易编号建立唯一约束,防止重复记账或重复发货。

按文档返回确认内容

有些网关要求回调接口返回特定文本,例如 OK、SUCCESS,或者指定的 HTTP 状态码。具体响应内容必须遵循网关文档,否则网关可能持续重试。

区分浏览器返回页和服务器通知

支付完成后的浏览器跳转页面并不一定可靠。用户可能关闭页面,页面参数也可能被修改。订单入账应优先依据支付网关提供的服务器端通知,必要时再主动调用网关接口查询交易状态。

备注:内容仅供参考。