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让后端实时把事件推给前端,步骤如下:
- 前端安装依赖:
npm install socket.io-client
- 修改
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
相关产品推荐
相关产品推荐

