如何在 VueJs 组件中获取 body 传递的参数值?
结论
Vue 组件的 created() 无法直接读取第三方支付页面跳转时携带的 HTTP request body。
request body 由接收请求的服务器处理,而 Vue 组件运行在浏览器中。页面跳转完成后,浏览器不会向页面 JavaScript 暴露原始 POST body。
支付网关应先把结果提交到后端回调地址。后端解析请求、验签并保存结果,然后将浏览器重定向到 Vue 页面。Vue 组件再通过订单号或一次性凭证向后端查询支付状态。
推荐处理流程
通常可以按以下流程处理:
- 支付网关向后端回调接口发送
POST请求。 - 后端解析请求体,并验证支付平台的签名。
- 后端根据商户订单号更新支付状态。
- 后端将浏览器重定向到 Vue 支付结果页。
- Vue 组件从 URL 中获取订单号或短期凭证。
- Vue 调用后端接口查询最终支付结果。
有些支付平台会提供两个不同的地址:
- 浏览器返回地址,用于将用户带回网站。
- 服务端通知地址,用于可靠地通知支付结果。
支付结果应以经过验签的服务端通知为准,不能只凭浏览器返回的数据确认支付成功。
后端接收支付结果
下面假设支付平台使用 application/x-www-form-urlencoded 提交数据,后端使用 Express:
import express from 'express';
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.post('/api/payment/callback', async (req, res) => {
const paymentData = req.body;
// 必须根据支付平台文档验证签名。
const signatureValid = verifyPaymentSignature(paymentData);
if (!signatureValid) {
return res.status(400).send('Invalid signature');
}
const orderId = paymentData.order_id;
const paymentStatus = paymentData.status;
// 应实现幂等更新,避免重复通知造成重复处理。
await updatePaymentStatus(orderId, paymentStatus);
// 只把非敏感标识放入 URL。
res.redirect(
303,
`/payment/result?orderId=${encodeURIComponent(orderId)}`
);
});
verifyPaymentSignature() 和字段名称需要根据实际支付网关的协议实现。示例中的 order_id 和 status 不能直接当作固定字段使用。
使用 303 See Other 重定向,可以明确让浏览器通过 GET 请求打开结果页面,避免沿用原来的 POST 方法。
在 Vue 组件中查询结果
如果项目使用 Vue Router,可以在 created() 中读取查询参数,再向后端发起请求:
export default {
data() {
return {
loading: true,
payment: null,
error: null
};
},
async created() {
const orderId = this.$route.query.orderId;
if (!orderId) {
this.loading = false;
this.error = '缺少订单号';
return;
}
try {
const response = await fetch(
`/api/payments/${encodeURIComponent(orderId)}`,
{
credentials: 'same-origin'
}
);
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
this.payment = await response.json();
} catch (error) {
this.error = '暂时无法获取支付结果,请稍后重试';
console.error(error);
} finally {
this.loading = false;
}
}
};
后端查询接口还应验证当前用户是否有权查看该订单:
app.get('/api/payments/:orderId', async (req, res) => {
const order = await findPaymentByOrderId(req.params.orderId);
if (!order) {
return res.status(404).json({
message: 'Payment not found'
});
}
// 还需要校验订单是否属于当前登录用户。
res.json({
orderId: order.id,
status: order.paymentStatus,
amount: order.amount
});
});
使用 Vue 3 Composition API 时,也可以在 setup() 或 onMounted() 中执行相同的查询逻辑。问题不在于选择哪个生命周期钩子,而在于支付请求体必须先由服务器接收。
如果数据实际在 URL 中
只有支付平台通过查询字符串返回数据时,前端才能直接读取。例如:
https://example.com/payment/result?orderId=123&status=success
在 Vue Router 中可以这样获取:
created() {
const orderId = this.$route.query.orderId;
const status = this.$route.query.status;
}
如果不使用 Vue Router,可以这样读取:
const params = new URLSearchParams(window.location.search);
const orderId = params.get('orderId');
const status = params.get('status');
用户可以自行修改 URL 中的 status=success,所以这个值只能用于页面展示或触发后端查询,不能作为修改订单状态的可信依据。
为什么不能在 created() 中读取 POST body
下面的写法在浏览器端并不存在:
created() {
console.log(request.body);
}
request 和 request.body 属于服务器端的请求上下文。Vue 生命周期钩子可以访问的内容包括:
- 组件状态;
- Vue Router 参数;
- URL 查询字符串;
localStorage或sessionStorage;- Cookie;
- 前端主动请求后端后得到的数据。
即使支付平台通过 HTML 表单向 Vue 路由发起 POST 请求,Vue 页面加载后仍然无法取得那次表单提交的原始 body。服务器必须先接收 POST 请求,再通过重定向、服务端会话或数据库把必要的信息传给前端。
注意事项
- 必须验证支付网关的签名、时间戳、金额、币种、商户订单号和商户身份。
- 不要把银行卡信息、签名、支付令牌或完整回调内容放进 URL。
- 支付回调可能重复发送,订单更新逻辑必须具备幂等性。
- 浏览器返回可能早于或晚于服务端通知,结果页应支持轮询或手动刷新状态。
- 不要只根据前端传回的
success字段发货、充值或确认订单。 - 如果前后端部署在不同域名下,还需要正确配置 CORS、Cookie 的
SameSite属性和 HTTPS。 - 如果支付平台要求回调接口返回特定文本或状态码,应严格按照接口文档响应。在这种情况下,服务端通知接口通常不应直接负责页面跳转。
备注:内容仅供参考。
