Blazor 如何通过 POST 请求跳转到第三方支付页面
结论
可以通过 POST 跳转到第三方支付页面,但 NavigationManager.NavigateTo() 只能执行普通页面导航,发起的是 GET 请求,无法携带 POST body。
浏览器通常通过 HTML 表单实现:
- 创建一个指向支付网关的
<form>。 - 将参数写入隐藏字段。
- 提交表单,由浏览器加载支付网关返回的 HTML 页面。
需要注意,POST 只能防止参数出现在 URL、浏览器历史记录和常规访问日志中,并不能向用户隐藏参数。用户仍然可以通过开发者工具查看表单字段和网络请求。商户密钥、签名私钥等敏感信息绝不能发送到 Blazor WebAssembly 客户端。
使用 HTML 表单提交
如果由用户点击按钮进入支付页面,可以直接在 Blazor 组件中使用普通表单:
<form action="https://sandbox.merchant.razer.com/RMS/pay/MerchantID/"
method="post">
<input type="hidden" name="Param1" value="@Param1" />
<input type="hidden" name="param2" value="@Param2" />
<input type="hidden" name="returnurl" value="@ReturnUrl" />
<input type="hidden" name="cancelurl" value="@CancelUrl" />
<button type="submit">前往支付</button>
</form>
@code {
private string Param1 = "data";
private string Param2 = "data";
private string ReturnUrl = "https://example.com/payment/return";
private string CancelUrl = "https://example.com/payment/cancel";
}
提交表单后,浏览器会向 action 指定的地址发送 POST 请求,并将网关返回的 HTML 显示为新页面。
浏览器表单默认使用以下编码格式:
application/x-www-form-urlencoded
只有在支付网关明确要求 multipart/form-data 时,才需要添加:
<form action="https://sandbox.merchant.razer.com/RMS/pay/MerchantID/"
method="post"
enctype="multipart/form-data">
不要只凭 Postman 中测试成功的配置判断编码格式,应以支付网关的接口文档为准。
自动提交并跳转
如果不想让用户再次点击按钮,可以通过 JavaScript 自动提交表单。
Blazor 组件:
@inject IJSRuntime JS
<form id="paymentForm"
action="https://sandbox.merchant.razer.com/RMS/pay/MerchantID/"
method="post">
<input type="hidden" name="Param1" value="@Param1" />
<input type="hidden" name="param2" value="@Param2" />
<input type="hidden" name="returnurl" value="@ReturnUrl" />
<input type="hidden" name="cancelurl" value="@CancelUrl" />
</form>
<p>正在跳转到支付页面……</p>
@code {
private bool _submitted;
private string Param1 = "data";
private string Param2 = "data";
private string ReturnUrl = "https://example.com/payment/return";
private string CancelUrl = "https://example.com/payment/cancel";
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && !_submitted)
{
_submitted = true;
await JS.InvokeVoidAsync("submitPaymentForm", "paymentForm");
}
}
}
在 wwwroot/js/site.js 中添加:
window.submitPaymentForm = function (formId) {
const form = document.getElementById(formId);
if (!form) {
throw new Error(`Form '${formId}' was not found.`);
}
HTMLFormElement.prototype.submit.call(form);
};
确认该脚本已经在 wwwroot/index.html 中加载:
<script src="js/site.js"></script>
使用 HTMLFormElement.prototype.submit.call(form),可以防止表单中名为 submit 的字段覆盖原生 submit() 方法。
不要用 HttpClient 模拟页面跳转
下面的代码虽然能发送 POST 请求,却不会让浏览器进入支付页面:
var response = await HttpClient.PostAsync(paymentUrl, content);
var html = await response.Content.ReadAsStringAsync();
这段代码只是让 Blazor 应用获取响应内容,浏览器地址不会切换到第三方页面。跨域请求还可能受到 CORS 限制,支付页面使用的 Cookie、重定向、脚本和相对资源也未必能在应用中正常运行。
如果网关要求浏览器通过 POST 进入支付页面,应提交表单,而不是调用 HttpClient.PostAsync()。
敏感参数应由服务器处理
Hosted Blazor WebAssembly 的客户端代码和数据都在用户浏览器中运行。以下内容不能安全地存放在组件、隐藏字段或客户端配置中:
- Merchant secret
- API secret
- 私钥
- 用于生成签名的密钥
- 可被重复使用的访问凭证
type="hidden" 只是让字段不直接显示在页面上,并不能保密。用户仍可通过 HTML、JavaScript、浏览器开发者工具或网络面板查看字段内容。
更安全的处理流程如下:
Blazor 客户端
→ 请求自己的 ASP.NET Core 后端创建支付
→ 后端校验订单金额和用户身份
→ 后端使用商户密钥生成签名或调用支付网关
→ 后端返回一次性支付 URL、session/token 或允许公开的表单字段
→ 浏览器跳转或提交支付表单
例如,客户端只需将内部订单编号发送给自己的后端:
var response = await HttpClient.PostAsJsonAsync(
"/api/payments/create",
new { OrderId = orderId });
var result = await response.Content
.ReadFromJsonAsync<CreatePaymentResult>();
后端必须根据数据库中的订单重新确定金额、币种和收款信息,不能信任客户端提交的金额。确认这些信息后,再按照网关协议生成签名或创建支付会话。
如果网关返回一次性支付地址,可以直接跳转:
NavigationManager.NavigateTo(result.PaymentUrl, forceLoad: true);
如果网关要求浏览器提交一组签名字段,这些字段应由后端生成,再由客户端通过表单提交。即使签名本身可以公开,用于计算签名的密钥也必须始终保存在服务器端。
其他注意事项
- 参数名称、大小写、字符编码和签名顺序必须严格遵循支付网关文档。
returnurl通常只负责支付完成后的页面跳转,不能单独作为支付成功的可信依据。- 订单的最终状态应由后端通过支付网关回调、Webhook 或服务端查询接口确认。
- 返回地址和取消地址应使用 HTTPS,并由服务端限制为可信域名,避免开放重定向问题。
- 如果站点配置了 Content Security Policy,需要确认
form-action允许向支付网关域名提交表单。 - 不要在前端日志、异常信息或分析系统中记录完整的支付参数。
- 前端应防止重复点击和重复提交,后端则要保证创建支付订单的操作具有幂等性。
页面需要通过 POST 跳转时,应提交 HTML 表单。所有需要保密或用于决定支付金额的逻辑,都应放在 ASP.NET Core 后端完成。
备注:内容仅供参考。
