安全指南
保护您的 PayerScan 支付网关集成的最佳实践。
API Key 安全
切勿暴露您的 API Key
API Key 用于验证发送到 PayerScan API 的请求。它绝不能对终端用户可见。
// ❌ 错误 - 从前端调用 API(API Key 对任何人可见)
fetch('https://api.payerscan.com/payment/crypto', {
headers: { 'x-api-key': 'EQnhBYpknGAP...' } // API Key 暴露!
})
// ✅ 正确 - 从后端服务器调用 API
// 前端调用您的内部 API
fetch('/api/create-payment', { body: { amount: 100 } })
// 您的后端调用 PayerScan(API Key 安全地存储在服务器)
app.post('/api/create-payment', async (req, res) => {
const result = await fetch('https://api.payerscan.com/payment/crypto', {
headers: { 'x-api-key': process.env.PAYERSCAN_API_KEY }
})
res.json(result)
})
安全存储 API Key
# ✅ 使用环境变量
PAYERSCAN_API_KEY=EQnhBYpknGAP...
# ❌ 不要在代码中硬编码
const API_KEY = 'EQnhBYpknGAP...' # 不要这样做
注意: 切勿将 API Key 提交到版本控制系统(Git)。请将
.env添加到.gitignore文件中。
定期轮换 API Key
- 如果怀疑 API Key 已泄露,请立即更换
- 进入商店管理 → 生成新的 API Key
- 立即在您的服务器上更新新的 API Key
- 生成新 Key 后,旧 API Key 将立即失效
Webhook 安全
使用 HTTPS
始终为 callback_url 使用 HTTPS,以加密传输中的数据并防止中间人攻击。
// ✅ 正确 - 使用 HTTPS
callback_url: 'https://your-server.com/webhook/payment'
// ❌ 错误 - 使用 HTTP(数据未加密,容易被拦截)
callback_url: 'http://your-server.com/webhook/payment'
验证 Webhook 请求
始终验证 webhook 请求确实来自 PayerScan:
app.post('/webhook/payment', express.json(), async (req, res) => {
try {
const { merchant_id, api_key, trans_id, request_id, status, amount, transaction_hash } = req.body
// 1. 验证 merchant_id(completed 和 expired 均适用)
if (merchant_id !== process.env.PAYERSCAN_MERCHANT_ID) {
return res.status(401).json({ error: 'Invalid merchant' })
}
// 2. 验证 api_key(仅 completed — expired 不包含 api_key)
if (status === 'completed' && api_key !== process.env.PAYERSCAN_API_KEY) {
return res.status(401).json({ error: 'Invalid API Key' })
}
// 3. 通过 request_id(如有)或 trans_id 查找订单
let order = null
if (request_id) {
order = await db.orders.findOne({ request_id })
}
if (!order) {
order = await db.orders.findOne({ trans_id })
}
if (!order) {
return res.status(404).json({ error: 'Order not found' })
}
// 4. 仅处理可更新状态的订单(幂等)
const processableStatuses = ['waiting', 'pending', 'processing']
if (!processableStatuses.includes(order.status)) {
return res.status(200).json({ message: 'Already processed' })
}
// 5. 验证金额是否匹配(仅 completed — 防止篡改)
if (status === 'completed') {
if (parseFloat(amount) !== parseFloat(order.amount)) {
return res.status(400).json({ error: 'Amount mismatch' })
}
}
// 6. 检查 transaction_hash 是否已使用(防止重放)
if (status === 'completed') {
const existingTx = await db.orders.findOne({ transaction_hash })
if (existingTx) {
return res.status(409).json({ error: 'Transaction hash already used' })
}
}
// 7. 原子更新 — 包含状态过滤以防止竞态条件
if (status === 'completed') {
const result = await db.orders.update(
{ trans_id, status: { $in: processableStatuses } },
{ status: 'completed', transaction_hash }
)
if (result.modifiedCount === 0) {
return res.status(200).json({ message: 'Already processed' })
}
} else if (status === 'expired') {
const result = await db.orders.update(
{ trans_id, status: { $in: processableStatuses } },
{ status: 'expired' }
)
if (result.modifiedCount === 0) {
return res.status(200).json({ message: 'Already processed' })
}
}
res.status(200).json({ success: true })
} catch (error) {
console.error('Webhook processing error:', error)
res.status(500).json({ error: 'Internal server error' })
}
})
幂等处理
Webhook 最多可重试 5 次。确保多次处理是安全的:
// ✅ 正确 - 仅更新可处理状态的订单
const processableStatuses = ['waiting', 'pending', 'processing']
if (!processableStatuses.includes(order.status)) {
return res.status(200).json({ message: 'Already processed' })
}
// ❌ 错误 - 无检查,可能多次添加余额
await addBalance(user, amount) // 如果 webhook 发送两次 → 余额添加两次!
数据安全
不要记录敏感数据
// ❌ 错误 - 记录 API Key
console.log('Request with API Key:', req.headers['x-api-key'])
// ✅ 正确 - 仅记录必要信息
console.log('Payment created:', { trans_id, amount, status })
验证输入
PayerScan 在服务器端验证输入,但您也应该在您的端进行验证:
// 验证金额(必须为正数,最高 1,000,000 USD)
const amount = parseFloat(req.body.amount)
if (isNaN(amount) || amount <= 0 || amount > 1000000) {
return res.status(400).json({ error: 'Invalid amount' })
}
// 验证 callback_url 格式
if (req.body.callback_url) {
try {
const url = new URL(req.body.callback_url)
if (url.protocol !== 'https:') {
console.warn('callback_url 应使用 HTTPS 以确保安全')
}
} catch {
return res.status(400).json({ error: 'Invalid callback_url' })
}
}
PayerScan 服务器端保护
PayerScan 在服务器端实施了多层安全保护:
- SSRF 防护: 系统在发送 webhook 之前验证所有
callback_url值。内部/私有 IP 和可疑 URL 会被自动阻止。 - 速率限制: API 端点通过突发和持续速率限制进行保护,以防止滥用。详见 速率限制。
- 输入验证: 所有请求参数都通过严格的 schema 进行验证(金额范围、URL 格式、字符串长度限制)。
- Webhook 超时: Webhook 回调有 10 秒超时。如果您的服务器在 10 秒内未响应,该次尝试将被标记为失败并将重试。
安全检查清单
上线前
- API Key 存储在环境变量中(未硬编码)
- API Key 未暴露在前端/客户端代码中
-
.env文件已添加到.gitignore - Webhook 端点使用 HTTPS
- Webhook 对 completed 和 expired 均验证
merchant_id - Webhook 对 completed webhook 验证
api_key - Webhook 通过
request_id或trans_id查找订单 - Webhook 处理幂等(不重复处理)
- Webhook 验证
amount与订单金额一致 - Webhook 检查
transaction_hash未被使用(防止重放) - Webhook 使用原子更新并包含状态过滤(防止竞态条件)
- 不记录敏感数据(API Key、私钥)
- 对金额、URL 进行输入验证
持续维护
- 定期审查 webhook 端点的访问日志
- 监控未授权或可疑的 webhook 请求
- 定期轮换 API Key 或在泄露时更换
- 更新包含安全补丁的依赖项
- 测试 webhook 端点能在 10 秒内响应
安全事件响应
如果 API Key 被泄露
- 立即进入商店管理 → 生成新的 API Key
- 在所有服务器上更新新的 API Key
- 审查最近的 API 日志以查找未授权请求
- 如发现可疑交易,联系支持团队
如果检测到伪造 Webhook
- 验证您的 webhook 处理程序是否检查了
merchant_id和api_key - 检查日志中是否有
amount不匹配或transaction_hash重复尝试 - 封锁来源 IP(如果可以确定)
- 如有可能,添加 IP 白名单
- 联系支持团队报告事件
联系支持
如有紧急安全问题,请立即联系我们: