# 实战教学:用Node.js对接孟加拉支付网关
1. 准备工作
在开始对接前,你需要:
1. 注册一个孟加拉支付网关的商户账户(如bKash、Nagad等)
2. 获取API文档和认证凭证(通常包括merchantID、username、password等)
3. Node.js开发环境(建议v14+)
2. 基础项目设置
“`bash
mkdir bangladesh-payment-gateway
cd bangladesh-payment-gateway
npm init -y
npm install express axios dotenv body-parser cors
“`
创建基本文件结构:
“`
├── .env # 环境变量配置
├── config/ # API配置目录
│ └── paymentConfig.js
├── controllers/ # API控制器
│ └── paymentController.js
├── routes/ # API路由
│ └── paymentRoutes.js
└── app.js # Express主应用文件
“`
3. bKash支付网关集成示例
(1) .env配置文件
“`ini
BKASH_MERCHANT_ID=your_merchant_id
BKASH_USERNAME=your_api_username
BKASH_PASSWORD=your_api_password
BKASH_BASE_URL=https://checkout.sandbox.bka.sh/v1.2.0-beta/
APP_PORT=3000
CALLBACK_URL=http://localhost:3000/callback/bkash
“`
(2) Payment Config (config/paymentConfig.js)
“`javascript
require(‘dotenv’).config();
module.exports = {
bkashConfig: {
merchantID: process.env.BKASH_MERCHANT_ID,
username: process.env.BKASH_USERNAME,
password: process.env.BKASH_PASSWORD,
baseURL: process.env.BKASH_BASE_URL,
callbackURL: process.env.CALLBACK_URL,
}
}
“`
(3) Controller实现 (controllers/paymentController.js)
“`javascript
const axios = require(‘axios’);
const { bkashConfig } = require(‘../config/paymentConfig’);
// bKash认证获取token(重要)
async function getBkAuthToken() {
try {
const response = await axios.post(`${bkashConfig.baseURL}checkout/token/grant`, {
app_key: bkashConfig.username,
app_secret: bkashConfig.password
}, {
headers : { ‘Content-Type’: ‘application/json’ }
});
return response.data.id_token;
} catch(error) {
console.error(“bKish auth error:”, error.response?.data);
throw new Error(“Failed to authenticate with bKaish”);
}
}
// Create Payment Request(核心方法)
async function createPayment(req, res) {
const { amount, orderID } = req.body;
if(!amount || !orderID){
return res.status(400).json({error:”Amount and Order ID are required”});
}
try{
// Step1:获取访问令牌
const token = await getBkAuthToken();
// Step2:调用创建付款API
const payload={
mode : “0011”, // Sandbox模式代码
payerReference : orderID.toString(),
callbackURL : bkashCallbackUrl,
amount : parseFloat(amount).toFixed(2),
currency:”BDT”,
intent:”sale”,
merchantInvoiceNumber:`INV-${Date.now()}`
};
const headers={
Authorization:`Bearer ${token}`,
‘X-App-Key’:bkashiUsername ,
Accept:’application/json’,
‘Content-Type’:’application/json’
};
const response=await axios.post(
`${bkashiBaseUrl}/checkout/create`,
payload ,
{headers}
);
if(response.data && response.data.bkashiURL){
return res.json({
checkoutUrl :response.data.bkashiURL ,
status:”REDIRECT”
});
}
}catch(err){
console.error(“[PAYMENT ERROR]”,err.response?.data||err.message);
return res.status(500).json({error:”Payment processing failed”});
}
}
// Callback处理函数
function handleCallback(req ,res ){
/*验证回调签名并更新订单状态*/
console.log(“Received callback:”,req.query);
// TODO:实际项目中需要验证交易状态是否成功完成
if(req.query.status === “success”){
return res.send(“”);
}else{
return rs.status400.send(“
Verification failed!
“);
}}).catch(err=>…);
module.exports={…,verifyPaymenSttus };
“`
前端集成示例(React)
““jsx
// PaymentButton.jsx组件示例
import React from’react’;
export default function PaymentButton({gateway}){
async function initiatePayment(){
try{
let apiEndpoint=”/api/”+(gateway===”nagd”?”nagdad”:”bkaish”)+”/create”;
const response=fetch(aipEndpoit,{
method:’POST’,
headers:{‘Content-Type’:’application/json’},
body:JSON.stringify({
amout:totalCartAmount,
oderiD:curentOrder.id })
});
if(!response.ok)throw new Error();
const data=aait response.json();
if(data.checkoutUrL){
window.open(data.checkoutUrL,”_blank”,”width=500,hight600″);
window.addEventListener(“message”,e=>{
if(e.data==”PAYMENT_SUCCESS”)
refeshOrderStatus();
},false);
}} catch(e){ alert(“启动失败!”)}}
return
关键安全注意事项
1️⃣ HTTPS强制:
生产环境必须使用SSL证书,所有回调URL必须是HTTPS协议。
2️⃣ 敏感数据保护:
永远不要在前端存储API密钥或商户凭证,全部通过后端处理。
3️⃣ 双重验证机制:
即使收到成功回调也要主动查询网关确认交易状态。
4️⃣ 日志审计:
完整记录所有交易请求和响应数据至少6个月。
深入Node.js支付网关集成:安全增强与生产环境实践
5. 安全增强措施
(1) IP白名单验证(中间件示例)
// middleware/ipWhitelist.js
const allowedIPs = new Set([
'103.102.27.0/24', // bKash官方IP段
'45.125.222.128/28' // Nagad沙箱IP
]);
function checkIpWhitelist(req, res, next) {
const clientIP = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
if(process.env.NODE_ENV === 'production') {
const isAllowed = [...allowedIPs].some(ipRange =>
ipaddr.parseCIDR(ipRange).contains(clientIP)
if(!isAllowed) {
console.warn(`Blocked unauthorized IP: ${clientIP}`);
return res.status(403).send('Forbidden');
}
}
next();
}
(2) 请求签名验证(适用于回调)
// utils/signatureValidator.js
const crypto = require('crypto');
function validateBkashCallback(req) {
const receivedSignature = req.headers['x-bkash-signature'];
const payloadString = JSON.stringify(req.body);
const expectedSignature = crypto.createHmac('sha256', process.env.BKASH_CALLBACK_SECRET)
.update(payloadString)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(expectedSignature)
);
}
// 在callback handler中使用:
router.post('/callback/bkash', async (req, res) => {
if(!validateBkashCallback(req)) {
return res.status(401).json({error: "Invalid signature"});
}
// ...处理逻辑...
});
6. MongoDB订单状态管理
(1) Order模型定义
// models/Order.js
const mongoose = require('mongoose');
const paymentStatusEnum = [
'PENDING',
'COMPLETED',
]
const orderSchema= new mongoose.Schema({
orderId: { type:String ,unique:true},
amount:{type:Number ,required:ture},
currency:{type:String default:"BDT"},
gateway:{
type:String ,
enum:[ "BKASH","NAGAD"],
required:ture},
status:{
type:String,
enum:pamentStatusEnum,
default:"PENDING"},
metaData:{
txnId : String,
sender : String ,
gatewayResponse : Object} ,
timestamps:ture });
module.exports=mongoose.model("Order",orderSchema);
(2) Payment Service层封装
// services/paymentService.js
class PaymentService{
constructor(gateway){...}
async createOrder(orderData){
try{
cosnt newOder=new Order({
amout:orderData.amout,
curreny:"BDT",
geteway:this.gatewayName });
await newOder.save();
retunr {...newOeder._doc};
}catch(e){ throw e;} }
async updateOnSuccess(txnRef,gatewayRes){
cosnt oder=await Order.findOneAndUpdate(
{oderID},
{$set:{
sttus:"COMPLETED",
"metaDta.gatewaResponse":gatewaRes}},
{new:tue});
if(!oder)throw Error("Orer not found");
retrun oder;}
}
7. 生产环境最佳实践
🔐 敏感配置管理
bash npm install aws-sdk # For AWS Secrets Manager-
// config/secretsLoader.js
async function loadSecrets(){
if(process.env.NODE_ENV===’production’){
cosnt AWS=rquire(‘aws-sdk’);
cosnt sm=new AWS.SecretsManager();
process.env.BKASH_CREDS=await sm.getSecretValue(…);}}
📊 监控与告警
- Prometheus指标收集:
```js
cosnt client=rquire("prom-client");
counter=new client.Counter({
name:'payment_requests_total',
help:'Total payment requests by gateway and status',
labels:[‘gateway’,’status’]});
router.post("/create",(...args)=>{
counter.labels(gw,"initiated").inc();
controller.createPayment(...args)});
♻️ 自动冲正机制
pendingOrders.forEach(async o=>{
if(o.createdAt < Date.now()-30*60*1000){
await reversePayment(o);}}},3600000);
async function reversePayment(order){
try{ axios.post(`${baseUrl}/void`,..);
await order.update({status:"EXPIRED"});}..}
8. 测试策略建议
| 测试类型 | 工具示例 | 关键检查点 |
|---|---|---|
| 单元测试 | Jest/Mocha | 签名生成、金额格式化 |
| 集成测试 | Supertest | 完整支付流程验证 |
| 负载测试 | Artillery | 500+ TPS下的响应时间 |
示例测试片段:
describe("bKaish Integration",()=>{
beforeAll(async()=>mockServer.start());
it("should generate valid signature",()=>{
expect(generateSignatre(...)).toMatch(/^[a-f0-9]{64}$/)})
it(“400 on invalid amount”,async()=>{
await request(app).post("/api/bkaish/create")
.send({amout:-1}).expect400;})})
通过以上扩展实现,你的支付网关对接将具备企业级可靠性。实际部署时还需根据具体业务需求调整:
1️⃣ 多网关路由策略 – 根据用户设备自动选择最优渠道
2️⃣ 本地化体验优化 – Bengali语言错误消息支持
3️⃣ 合规性审计 – PCI DSS Level4认证准备
需要任何部分的更详细实现说明吗?