MVC 4 支付网关集成如何携带 POST 参数跳转
支付老李
结论
Redirect(request) 用错了。Redirect 接收目标 URL,并返回 302 重定向响应,不会解析或提交传入的 HTML 表单。当前代码把整段 <form> 和 <script> 当作 URL 处理,所以服务器返回 HTTP 400 Invalid URL。
如果支付网关要求通过 POST 携带参数,Controller 应返回一个带有隐藏字段的 HTML 页面,再由浏览器自动提交表单。
推荐做法:使用 Razor View 生成表单
先定义页面模型:
using System.Collections.Generic;
public class PaymentPostViewModel
{
public string ActionUrl { get; set; }
public IDictionary<string, string> Fields { get; set; }
}
Controller 生成支付参数,然后返回中转页面:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult OrderSummary(OrderSummaryViewModel order)
{
// 金额、订单号等信息应从服务端重新查询或计算,
// 不要直接信任浏览器提交的数据。
var fields = new Dictionary<string, string>
{
{ "merchant_id", "your-merchant-id" },
{ "order_id", order.OrderId },
{ "amount", "100.00" },
{ "signature", GenerateSignature(order) }
};
var model = new PaymentPostViewModel
{
ActionUrl = "https://payment.example.com/pay",
Fields = fields
};
return View("PostToPaymentGateway", model);
}
创建 PostToPaymentGateway.cshtml:
@model PaymentPostViewModel
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>正在跳转到支付页面</title>
</head>
<body>
<p>正在跳转到支付页面,请稍候……</p>
<form id="paymentForm"
method="post"
action="@Model.ActionUrl"
accept-charset="utf-8">
@foreach (var field in Model.Fields)
{
<input type="hidden"
name="@field.Key"
value="@field.Value" />
}
<noscript>
<p>浏览器未启用 JavaScript,请点击下面的按钮继续。</p>
<button type="submit">继续支付</button>
</noscript>
</form>
<script>
document.getElementById("paymentForm").submit();
</script>
</body>
</html>
浏览器收到页面后,会先渲染带有预填充参数的表单。随后,JavaScript 调用 submit(),浏览器直接向 ActionUrl 发起 POST 请求,支付网关从隐藏字段中接收参数。
Razor 默认会对属性值进行 HTML 编码。不要随意使用 Html.Raw() 输出支付参数,否则可能带来 HTML 注入或 XSS 风险。
如果必须保留 PreparePOSTForm
如果现有方法已经能生成完整且安全的 HTML,可以通过 Content 返回,不要调用 Redirect:
[HttpPost]
public ActionResult OrderSummary()
{
string request = PreparePOSTForm(
"https://payment.example.com/pay",
paymentData
);
return Content(request, "text/html", Encoding.UTF8);
}
还需要添加:
using System.Text;
不过,用字符串拼接 HTML 很容易遗漏编码。如果 URL、字段名或字段值中含有引号、&、<、>,页面结构可能被破坏。因此,一般更适合用 Razor View 生成表单。
为什么普通重定向不能完成 POST
典型的 Redirect 会生成类似响应:
HTTP/1.1 302 Found
Location: https://payment.example.com/pay
浏览器随后通常会用 GET 请求访问 Location,而重定向响应本身也不会提供新的支付表单字段。
307 或 308 在某些场景下可以保留原请求的方法和请求体,但不便于把当前请求转换成支付网关要求的另一组字段,支付接口也未必支持这种调用方式。因此,它们不能代替表单中转页。
注意事项
- 支付金额、订单号、商户号和签名必须由服务端生成或校验。
- 支付网关地址应来自受信任的配置,不能由客户端任意指定,否则可能造成开放重定向或数据泄露。
- 生成签名时,必须严格遵守网关规定的字段顺序、字符编码和空值处理方式。
- 不要记录银行卡号、验证码、完整密钥等敏感数据。
- 除了自动提交,还应保留手动提交按钮,以免用户禁用 JavaScript 后无法继续。
- 支付结果应以后端通知或主动查询为准,不能只根据浏览器返回的页面判断。
备注:内容仅供参考。
