中级15 分钟阅读
API Key 模式 - HTTP API 集成
使用 API Key 在服务端通过 HTTP API 创建和管理订单,适合需要完整控制支付流程的场景。
获取 API Key
- 登录 PonponPay 商户后台
- 进入「API 密钥」页面
- 点击「创建密钥」
- 安全保存生成的 API Key
⚠️ API Key 只会显示一次,请立即保存。如果丢失,需要重新生成。永远不要在前端代码、Git 仓库或日志中暴露 API Key。
API 接口文档
基础信息
| Base URL | https://api.ponponpay.com/pay |
| 认证方式 | Bearer Token (API Key) |
| 数据格式 | JSON |
认证方式
所有 API 请求需要在 Header 中携带 API Key:
| Header | 值 |
|---|---|
Authorization | Bearer YOUR_API_KEY |
Content-Type | application/json |
创建订单
POST /order/add
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
currency | string | ✅ | 币种 (USDT/USDC/BUSD) |
network | string | ✅ | 网络 (tron/ethereum/bsc/polygon/solana) |
amount | number | ✅ | 金额 |
mch_order_id | string | ❌ | 商户订单号(最长32位,未传则自动生成) |
notify_url | string | ❌ | Webhook 回调地址 |
redirect_url | string | ❌ | 支付完成跳转地址 |
响应参数
| 参数 | 类型 | 说明 |
|---|---|---|
trade_id | string | PonponPay 交易号 |
currency | string | 币种 |
network | string | 区块链网络 |
amount | number | 订单金额 |
actual_amount | number | 实际需支付金额(保留4位小数) |
address | string | 收款钱包地址 |
expiration_time | number | 过期时间戳(秒) |
payment_url | string | 支付页面 URL |
代码示例
curl -X POST https://api.ponponpay.com/pay/order/add \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"currency": "USDT",
"network": "tron",
"amount": 100.00,
"mch_order_id": "ORDER_123456",
"notify_url": "https://your-site.com/webhook",
"redirect_url": "https://your-site.com/success"
}'响应示例
{
"code": 0,
"message": "success",
"data": {
"trade_id": "PP202412110001",
"currency": "USDT",
"network": "tron",
"amount": 100.00,
"actual_amount": 100.0001,
"address": "TXxx...xxx",
"expiration_time": 1704067200,
"payment_url": "https://checkout.ponponpay.com/status/PP202412110001"
}
}查询订单
POST /order/detail
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
trade_id | string | * | PonponPay 交易号 |
mch_order_id | string | * | 商户订单号 |
* trade_id 和 mch_order_id 任意传其中一个
响应示例
curl -X POST https://api.ponponpay.com/pay/order/detail \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"trade_id": "PP202412110001"
}'Webhook 回调
当订单状态变化时,我们会向你配置的 Webhook URL 发送 HTTP POST 请求。
状态类型
| status 值 | 描述 |
|---|---|
1 | 等待支付 |
2 | 支付成功 |
3 | 订单已过期 |
4 | 订单已取消 |
5 | 人工充值(按支付成功处理) |
验证 Webhook 签名
回调验签请以本文示例与官方最新文档为准。协议后续可能升级,升级时请同步更新服务端验签模块。请勿在前端保存 API Key 或将验签密钥暴露到客户端。
推荐验证流程(服务端)
- 只允许服务端接收回调,前端不参与鉴权。
- 先做来源与时效校验,过期或重复请求直接拒绝。
- 使用官方插件或内部封装方法完成请求完整性校验。
- 验证通过后再处理业务,并按订单号做幂等(重复回调只处理一次)。
- 校验失败返回 401,格式错误返回 400,成功返回 200/OK。
import crypto from 'crypto';
import express from 'express';
import { markEventProcessed } from './idempotency-store';
const app = express();
const apiKey = process.env.PONPONPAY_API_KEY || '';
const nonceStore = new Map(); // 生产建议换 Redis
app.use(express.json({
type: 'application/json',
verify: (req, _res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
function verifyPonponPayWebhook(req) {
const prefix = String(req.headers['x-key-prefix'] || '');
const timestamp = String(req.headers['x-timestamp'] || '');
const nonce = String(req.headers['x-nonce'] || '');
const signature = String(req.headers['x-signature'] || '').toLowerCase();
const rawBody = req.rawBody || '';
if (!apiKey || !prefix || !timestamp || !nonce || !signature) return false;
if (!/^\d+$/.test(timestamp)) return false;
const ts = Number(timestamp);
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - ts) > 300) return false;
if (prefix !== apiKey.slice(0, 12)) return false;
if (!/^[A-Za-z0-9]{16,128}$/.test(nonce)) return false;
const nonceKey = `${timestamp}:${nonce}`;
if (nonceStore.has(nonceKey)) return false;
nonceStore.set(nonceKey, Date.now());
setTimeout(() => nonceStore.delete(nonceKey), 10 * 60 * 1000);
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
const payload = `${timestamp}\n${nonce}\n${rawBody}`;
const expected = crypto.createHmac('sha256', keyHash).update(payload).digest('hex');
const a = Buffer.from(signature, 'utf8');
const b = Buffer.from(expected, 'utf8');
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
app.post('/webhook', express.json({ type: 'application/json' }), async (req, res) => {
if (!verifyPonponPayWebhook(req)) {
return res.status(401).send('Unauthorized');
}
const event = req.body;
if (!event?.order_no || event?.status === undefined) {
return res.status(400).send('Bad Request');
}
// 幂等保护:相同订单事件只处理一次
const firstTime = await markEventProcessed(event.order_no, event.status);
if (!firstTime) {
return res.status(200).send('OK');
}
switch (Number(event.status)) {
case 2:
case 5:
await handleOrderPaid(event.data);
break;
case 3:
await handleOrderExpired(event.data);
break;
case 4:
await handleOrderCancelled(event.data);
break;
default:
break;
}
res.status(200).send('OK');
});错误码
| 错误码 | 说明 |
|---|---|
0 | 成功 |
10001 | 参数错误 |
10002 | 签名错误 |
10003 | 订单不存在 |
10004 | 商户已禁用 |
10005 | API Key 无效 |
安全最佳实践
- 保护 API Key:永远不要在前端代码、Git 仓库或日志中暴露 API Key
- 使用环境变量:将 API Key 存储在环境变量中
- 验证 Webhook 签名:始终验证 Webhook 请求的签名
- 使用 HTTPS:确保你的 Webhook 端点使用 HTTPS
- 实现幂等性:Webhook 可能会重复发送,处理逻辑要保证幂等性