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 状态码。具体响应内容必须遵循网关文档,否则网关可能持续重试。
区分浏览器返回页和服务器通知
支付完成后的浏览器跳转页面并不一定可靠。用户可能关闭页面,页面参数也可能被修改。订单入账应优先依据支付网关提供的服务器端通知,必要时再主动调用网关接口查询交易状态。
备注:内容仅供参考。
