You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular应用如何创建安全POST端点接收Moip Payments API Webhooks?

如何在Angular应用中安全接收Moip Payments API的Webhooks

首先得给你提个关键注意点:纯前端Angular应用是没法直接作为Webhook接收端点的——因为前端代码运行在用户浏览器里,没有公网可访问的固定IP,而且还会遇到跨域问题。所以我们得结合后端服务(或者Angular Universal服务端渲染)来实现,同时通过Moip的签名验证确保只有官方请求能进入你的系统。

下面一步步给你拆解方案:

1. 先搞定「公网可访问的安全端点」

我推荐两种方案,其中方案B更简单通用:

方案B:搭建Node.js/Express后端中转(推荐)

用Node.js快速写一个小后端,专门负责接收Moip的Webhook请求,验证合法性后再通知你的Angular前端。这样前端只需要和自己的后端交互,安全又方便。

后端核心代码(带签名验证)

const express = require('express');
const crypto = require('crypto');
const app = express();
const PORT = process.env.PORT || 3000;

// 从环境变量取Moip密钥,绝对不要硬编码!
const MOIP_SECRET = process.env.MOIP_SECRET;

// 给Webhook端点单独用raw中间件,保留原始请求体(用来计算签名)
app.use('/moip-webhook', express.raw({ type: 'application/json' }));
// 其他路由用JSON解析
app.use(express.json());

// Webhook接收接口
app.post('/moip-webhook', (req, res) => {
  // 取出Moip请求里的签名头
  const moipSignature = req.headers['x-moip-signature'];
  // 获取原始请求体字符串
  const rawBody = req.body.toString();

  // 用自己的Moip密钥计算HMAC签名
  const calculatedSignature = crypto
    .createHmac('sha256', MOIP_SECRET)
    .update(rawBody)
    .digest('base64');

  // 签名不匹配直接拒绝请求
  if (calculatedSignature !== moipSignature) {
    return res.status(403).send('Invalid signature - not from Moip');
  }

  // 验证通过,解析事件内容
  const moipEvent = JSON.parse(rawBody);
  console.log('Received valid Moip event:', moipEvent.type);

  // 这里可以做两件事:
  // 1. 把事件存入数据库,供前端轮询获取
  // 2. 用WebSocket实时推送给Angular前端(体验更好)
  // 比如用Socket.io的话:io.emit('new-moip-event', moipEvent);

  // 返回200给Moip,确认已接收
  res.status(200).send('Event received successfully');
});

app.listen(PORT, () => {
  console.log(`Backend running on port ${PORT}`);
});

方案A:用Angular Universal创建服务端端点

如果你已经在用Angular Universal(服务端渲染),可以直接在server.ts里添加Webhook接收逻辑,本质和后端中转类似,只是把端点集成在Angular的服务端代码里:

// server.ts 片段
import * as express from 'express';
import crypto from 'crypto';

// ... 其他Angular Universal配置代码

const MOIP_SECRET = process.env.MOIP_SECRET;

// 处理Webhook的端点
app.post('/moip-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const moipSignature = req.headers['x-moip-signature'] as string;
  const rawBody = req.body.toString();

  const calculatedSignature = crypto
    .createHmac('sha256', MOIP_SECRET)
    .update(rawBody)
    .digest('base64');

  if (calculatedSignature !== moipSignature) {
    return res.status(403).send('Invalid signature');
  }

  const moipEvent = JSON.parse(rawBody);
  // 处理事件:存库或通知前端
  console.log('Moip event received:', moipEvent);

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

// ... 其他路由配置

2. Angular前端如何获取事件

后端搞定后,前端可以通过两种方式获取Moip的事件:

方式1:定时轮询后端(简单易实现)

优化你提供的moip-service.ts,让它调用后端的接口获取事件:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from '../environments/environment';
import { ToastrService } from 'ngx-toastr';
import { interval } from 'rxjs';
import { switchMap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class MoipService {
  // 后端提供的获取事件的接口
  private eventsApiUrl = `${environment.backendUrl}/moip-events`;

  constructor(
    private toastr: ToastrService,
    private http: HttpClient
  ) { }

  // 获取最新事件
  getNotifications() {
    return this.http.get<any[]>(this.eventsApiUrl);
  }

  // 启动定时轮询(比如每30秒查一次)
  startPolling(intervalMs: number = 30000) {
    interval(intervalMs)
      .pipe(switchMap(() => this.getNotifications()))
      .subscribe({
        next: events => {
          // 对新事件做提示或处理
          events.forEach(event => {
            this.toastr.info(`新Moip事件: ${event.type}`, '通知');
          });
        },
        error: err => {
          console.error('获取通知失败:', err);
          this.toastr.error('无法获取Moip通知', '错误');
        }
      });
  }
}

方式2:WebSocket实时推送(体验更好)

用Socket.io让后端实时把事件推给前端,步骤如下:

  1. 前端安装依赖:
npm install socket.io-client
  1. 修改moip-service.ts:
import { Injectable, OnDestroy } from '@angular/core';
import { io, Socket } from 'socket.io-client';
import { environment } from '../environments/environment';
import { ToastrService } from 'ngx-toastr';

@Injectable({
  providedIn: 'root'
})
export class MoipService implements OnDestroy {
  private socket: Socket;

  constructor(private toastr: ToastrService) {
    // 连接到后端的Socket.io服务
    this.socket = io(environment.backendUrl);

    // 监听后端推送的Moip事件
    this.socket.on('new-moip-event', (event) => {
      console.log('实时收到Moip事件:', event);
      this.toastr.info(`新Moip事件: ${event.type}`, '通知');
    });
  }

  // 组件销毁时断开连接
  ngOnDestroy() {
    this.socket.disconnect();
  }
}

3. 部署与测试注意事项

  • 部署后端/Universal应用:把服务部署到公网可访问的平台(比如Vercel、Heroku、AWS),拿到公网URL。
  • 配置Moip后台:在Moip沙箱/生产后台的通知设置里,添加你的Webhook端点(比如https://your-domain.com/moip-webhook),并选择需要接收的事件类型。
  • 安全强化:
    • 一定要用HTTPS,防止请求被篡改。
    • 除了签名验证,还可以在服务器防火墙里添加Moip的IP白名单(Moip官方会公布其IP范围)。
    • 所有密钥都用环境变量存储,绝对不要提交到代码仓库。

内容的提问来源于stack exchange,提问作者Luiz Ricardo Cardoso

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:31:31