Node.js 示例
完整的 Node.js 集成示例:MD5 排序签名、创建收款/付款订单、验证 webhook。
安装
只用了 Node 内置的 crypto、fetch 和 express,无需额外 SDK。
配置
# .env
API_BASE=https://api.168tonpay.xyz/api/v1/compat/godapay
APPLY_BASE=https://apply.168tonpay.xyz/api/v1/compat/godapay
MERCHANT_ID=aef6025b-f435-443c-bb18-f10e65d9314c
API_SECRET=...
签名工具
// gateway/auth.js
const crypto = require('crypto');
/**
* GodaPay 兼容 MD5 排序签名。
* 排除 signature 与空值字段,按 key 升序拼接,追加 &key=SECRET,MD5 小写十六进制。
*/
function sign(params, apiSecret) {
const signStr = Object.keys(params)
.filter((k) => k !== 'signature' && params[k] != null)
.sort()
.map((k) => `${k}=${params[k]}`)
.join('&') + `&key=${apiSecret}`;
return crypto.createHash('md5').update(signStr).digest('hex').toLowerCase();
}
module.exports = { sign };
API 客户端
// gateway/client.js
const { sign } = require('./auth');
class GatewayError extends Error {
constructor(code, message) { super(message); this.code = code; }
}
async function post(base, path, params) {
const body = { ...params };
body.signature = sign(body, process.env.API_SECRET);
const res = await fetch(`${base}${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const json = await res.json();
if (json.code !== 0) {
throw new GatewayError(json.code, json.message);
}
return json.data;
}
const API = process.env.API_BASE;
const APPLY = process.env.APPLY_BASE;
module.exports = {
GatewayError,
createCollectOrder: (d) => post(API, '/pay/order/create', d),
queryOrder: (d) => post(API, '/pay/order/query', d),
getBalance: () => post(API, '/pay/balance', { merchantId: process.env.MERCHANT_ID }),
createPayoutOrder: (d) => post(APPLY, '/apply/create', d),
};
创建收款订单
const gateway = require('./gateway/client');
async function startDeposit(player, orderNo) {
const { payUrl, platformOrderNo } = await gateway.createCollectOrder({
merchantId: process.env.MERCHANT_ID,
orderNo,
returnUrl: 'https://yourgame.com/api/webhooks/payment',
playerPhone: player.phone, // 玩家已登记号码,风控关键字段
playerRef: player.id,
});
// 为玩家打开托管页;等待 webhook 上分
return { payUrl, platformOrderNo };
}
创建付款订单
async function startWithdrawal(player, outOrderNo, amount) {
return gateway.createPayoutOrder({
merchantId: process.env.MERCHANT_ID,
outOrderNo,
amount: amount.toFixed(2),
bankType: 'BKASH',
userName: player.name,
account: player.phone,
returnUrl: 'https://yourgame.com/api/webhooks/payout',
});
}
Webhook 处理
const express = require('express');
const { sign } = require('./gateway/auth');
const app = express();
// webhook 是 form-urlencoded
app.use(express.urlencoded({ extended: false }));
app.post('/api/webhooks/payment', async (req, res) => {
const { signature, ...fields } = req.body;
if (sign(fields, process.env.API_SECRET) !== signature) {
return res.status(401).send('invalid signature');
}
// status: '1' = 已支付
if (req.body.status === '1') {
// 用 platformOrderNo 幂等去重,用 amount 上分
await creditPlayer(req.body.orderNo, req.body.amount).catch(console.error);
}
res.send('SUCCESS'); // 必须应答字面量 SUCCESS
});
错误处理
const { GatewayError } = require('./gateway/client');
try {
await gateway.createCollectOrder(data);
} catch (err) {
if (err instanceof GatewayError) {
// code 非 0 即业务错误;据 message / code 处理
throw new Error(`[${err.code}] ${err.message}`);
}
throw err;
}
测试
请平台把你的商户标记为 demo 商户,创建订单时带 isTest: true,平台会注入合成 SMS 并触发真实格式的 webhook。详见 测试凭证。