PAYATHON 2026

AngularJs 2 如何处理外部服务的 POST callback

支付阿杰

明确结论

Angular 2 无法直接接收外部服务发起的 HTTP POST callback。

Angular 是运行在浏览器中的前端框架。接收请求、解析 POST body 并返回响应,是 HTTP 服务器的工作。Angular Router 只处理浏览器端的 URL 导航,不是服务器路由。因此,当支付平台向 /paymentlanding-page 发送 POST 时,请求还没有进入 Angular,就会被 IIS、静态文件服务器或托管平台以 405 Method Not Allowed 拒绝。

如果完全不能使用服务端接口,只有支付平台支持以下任一方式时,集成才有可能完成:

  • 将 callback 改为 GET 重定向;
  • 使用支付平台提供的 JavaScript SDK,通过 postMessage 通知父页面;
  • 通过支付平台提供的状态查询接口,由现有的可信服务端查询;
  • 将 callback 指向支付平台或第三方托管的中转页面,再通过 postMessage 通知 Angular。

如果支付平台只支持向商户地址发送 POST,就没有可靠且安全的纯 Angular 实现。

为什么 Angular Router 接收不到 POST

假设配置了以下路由:

const routes: Routes = [
  {
    path: 'paymentlanding-page',
    component: PaymentLandingPageComponent
  }
];

这段配置只能处理 Angular 应用加载后的客户端导航,例如:

GET /paymentlanding-page

支付平台发起的通常是一次表单提交:

POST /paymentlanding-page HTTP/1.1
Content-Type: application/x-www-form-urlencoded

请求会先到达 Web 服务器。响应头中的:

Allow: GET, HEAD, OPTIONS, TRACE

说明服务器没有为该地址开放 POST。服务器直接返回 405 后,Angular 应用不会启动,PaymentLandingPageComponent 自然也无法执行。

即使修改 IIS 或静态托管规则,让服务器接受 POST 并返回 index.html,Angular 通常还是无法取得原始请求体。服务器需要先解析或转交数据,因此,仅仅允许 POST 并不能解决 callback 的处理问题。

推荐方案:由服务端接收 callback

常见做法是提供一个 HTTPS endpoint:

POST https://merchant.example.com/api/payment/callback

处理过程一般包括:

  1. 服务端接收支付平台发送的数据。
  2. 验证签名、时间戳、订单号和金额。
  3. 按照支付平台的要求返回成功响应。
  4. 更新订单状态,并避免重复 callback 导致重复入账。
  5. Angular 页面通过订单查询接口获取最终状态。

前端不能只根据浏览器收到的返回参数判断支付成功。支付结果涉及金额和订单状态,必须在可信环境中验证。

下面的代码仅用于说明服务端接口的大致形态:

app.post('/api/payment/callback', express.urlencoded({ extended: false }), async (req, res) => {
  const callback = req.body;

  if (!verifyPaymentSignature(callback)) {
    return res.status(400).send('invalid signature');
  }

  await updateOrderIdempotently(callback.orderId, callback.status);

  return res.status(200).send('OK');
});

实际使用的签名算法、响应正文和 HTTP 状态码,应以支付平台的协议为准。

如果支付平台支持 GET 返回地址

如果这里所说的 callback 只是用户付款后的浏览器跳转,而且支付平台允许配置 GET returnUrl,可以把返回地址设为 Angular 路由:

https://merchant.example.com/paymentlanding-page
    ?orderId=12345
    &status=pending

在 Angular 中读取参数:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-payment-landing-page',
  template: `<p>{{ message }}</p>`
})
export class PaymentLandingPageComponent implements OnInit {
  message = '正在确认支付结果…';

  constructor(private route: ActivatedRoute) {}

  ngOnInit(): void {
    const orderId = this.route.snapshot.queryParamMap.get('orderId');

    if (!orderId) {
      this.message = '缺少订单信息';
      return;
    }

    // 应向可信服务端查询订单状态,
    // 不要直接信任 URL 中的 status、amount 等参数。
  }
}

服务器还需要配置 SPA fallback,以便用户直接访问该路由时能够获得 index.html。不过,fallback 只适用于 GET,不会让 Angular 读取 POST body。

iframe 可以使用 postMessage 的情况

如果支付平台提供受支持的 iframe SDK,或者允许配置一个可控的 callback 中转页,可以通过 window.postMessage() 通知父页面。

父级 Angular 页面监听消息:

import { Component, HostListener } from '@angular/core';

@Component({
  selector: 'app-payment',
  template: `
    <iframe
      src="https://payments.example-provider.com/checkout"
      title="Payment">
    </iframe>
  `
})
export class PaymentComponent {
  private readonly paymentOrigin =
    'https://payments.example-provider.com';

  @HostListener('window:message', ['$event'])
  onMessage(event: MessageEvent): void {
    if (event.origin !== this.paymentOrigin) {
      return;
    }

    if (
      !event.data ||
      event.data.type !== 'payment.completed' ||
      typeof event.data.orderId !== 'string'
    ) {
      return;
    }

    const orderId = event.data.orderId;

    // 收到消息后查询可信来源,确认订单的真实支付状态。
    console.log('Payment notification received:', orderId);
  }
}

iframe 页面发送消息:

window.parent.postMessage(
  {
    type: 'payment.completed',
    orderId: '12345'
  },
  'https://merchant.example.com'
);

使用这种方案需要满足两个条件:

  • iframe 中的页面由己方控制,或者支付平台明确提供相应的 SDK;
  • 必须严格验证 event.origin 和消息结构,生产环境的目标来源不能使用 '*'。

由于浏览器的同源策略限制,父页面无法直接读取第三方 iframe 的 URL、DOM 或表单内容。只监听 iframe 的 load 事件,也只能得知页面发生了加载,无法可靠判断支付结果。

必须只有 Angular 时的可行边界

如果完全没有服务端能力,可以向支付平台确认是否支持以下方式:

  1. 将浏览器返回方式从 POST 改为 GET;
  2. 使用官方 JavaScript SDK 或 postMessage 事件;
  3. 将结果返回到支付平台托管的完成页;
  4. 使用现有订单系统提供的支付状态查询能力,不再新增 callback API;
  5. 使用支付平台支持的托管回调、中转服务或 serverless endpoint。

如果这些方式都不受支持,而且支付平台要求向商户 URL 提交 POST,就必须增加一个能够接收 POST 的服务端 endpoint。Angular 的 HttpClient 只能主动发送请求,无法把浏览器变成 HTTP 服务器,也无法监听外部系统发来的请求。

安全与集成注意事项

  • 不要根据 query string、iframe 事件或页面跳转直接把订单标记为支付成功。
  • callback 必须验证支付平台签名,不能只检查订单号或状态字段。
  • callback 可能被重复发送,因此订单更新必须具备幂等性。
  • 应校验订单金额、币种、商户号和支付交易号。
  • 不要把支付平台密钥、签名私钥或查询凭证放进 Angular 代码。
  • iframe 是否符合相应的 PCI DSS 范围要求,取决于支付平台提供的集成方式和合规方案,不能仅凭使用了 iframe 来判断。

当前的 405 不是 Angular 路由配置错误,而是目标服务器不接受 POST。合理的处理方式是由服务端接收并验证 callback。如果确实无法增加服务端,就需要调整支付平台的返回协议,改用 GET、官方 iframe SDK 或 postMessage 等平台支持的浏览器端机制。

备注:内容仅供参考。