PAYATHON 2026

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 后无法继续。
  • 支付结果应以后端通知或主动查询为准,不能只根据浏览器返回的页面判断。

备注:内容仅供参考。