Angular 1.5 重定向后如何获取 HTTP request 的 form data
支付小周
结论
AngularJS 1.5 和页面中的普通 JavaScript 都无法直接读取触发当前页面跳转的 HTTP POST body。
支付服务器提交的 form data 会发送到 callback URL 对应的服务端接口。通常应按以下流程处理:
- 服务端接收并验证支付平台提交的数据。
- 服务端保存支付结果。
- 服务端返回
303重定向,浏览器随后通过 GET 请求进入支付结果页。 - AngularJS 调用后端 API 查询支付状态。
为什么前端无法直接获取
假设支付服务器发送了以下请求:
POST /payment/callback HTTP/1.1
Content-Type: application/x-www-form-urlencoded
orderId=12345&status=success&signature=...
请求 body 中的数据由 /payment/callback 对应的服务器接收。浏览器加载服务器返回的 HTML 后,不会把之前的 POST body 提供给页面 JavaScript。
所以下面这些方式都读不到该 POST body:
window.location
window.location.search
document.forms
window.location.search 只能读取 URL 中的查询参数,例如:
/payment/result?orderId=12345
AngularJS 的 $http 只能发起新请求,无法读取浏览器此前在页面跳转时提交的请求内容。
推荐实现
下面以 Express 为例,其他后端框架也可以采用相同的处理方式。
1. 服务端接收支付回调
const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: false }));
app.post('/payment/callback', async function (req, res) {
const paymentData = req.body;
// 必须根据支付平台的规则验证签名
const valid = verifyPaymentSignature(paymentData);
if (!valid) {
return res.status(400).send('Invalid signature');
}
// 应根据交易号做幂等处理,避免重复回调导致重复入账
await savePaymentResult({
orderId: paymentData.orderId,
transactionId: paymentData.transactionId,
status: paymentData.status
});
// 使用 303,明确要求浏览器下一步以 GET 方式访问结果页
res.redirect(303, `/payment/result?orderId=${encodeURIComponent(paymentData.orderId)}`);
});
verifyPaymentSignature() 的实现和各个字段的名称取决于支付平台,示例中的参数不能直接照搬。
2. AngularJS 查询支付结果
结果页只携带订单标识,不直接采信 URL 中的支付状态:
angular.module('paymentApp').controller('PaymentResultController', function (
$scope,
$http,
$location
) {
var orderId = $location.search().orderId;
if (!orderId) {
$scope.error = '缺少订单编号';
return;
}
$http.get('/api/payments/' + encodeURIComponent(orderId) + '/status')
.then(function (response) {
$scope.payment = response.data;
})
.catch(function () {
$scope.error = '无法获取支付结果';
});
});
对应的服务端查询接口返回已经确认的支付状态:
app.get('/api/payments/:orderId/status', async function (req, res) {
const payment = await findPayment(req.params.orderId);
if (!payment) {
return res.status(404).json({
error: 'Payment not found'
});
}
res.json({
orderId: payment.orderId,
status: payment.status
});
});
如果只有纯前端页面
如果 callback URL 指向静态 HTML,没有服务端处理能力,就无法接收和处理 POST form data。此时可以:
- 增加一个后端 callback 接口;
- 使用支付平台提供的 server-to-server webhook;
- 或确认支付平台是否支持在跳转时通过查询参数传递非敏感标识。
支付状态、金额以及交易是否成功,不能只放在查询参数中交给前端判断,因为用户可以自行修改 URL。
注意事项
- 必须验证支付平台的签名、交易号、商户号和订单金额。
- 支付平台可能重复发送通知,因此回调接口需要支持幂等处理。
- 前端显示”支付成功”之前,应查询服务端已经确认并保存的状态。
- 如果 callback 接口受 CSRF 中间件保护,需要为支付回调设置适当的例外规则,但仍然必须验证支付签名。
- 不要把密钥、完整支付凭证或敏感用户信息放入查询参数。
- 浏览器跳转回调和 webhook 最好分开处理。最终支付状态应以经过验证的服务端通知或主动查询结果为准。
备注:内容仅供参考。
