Research Illustration

bKash集成完整指南

bKash集成完整指南

📋 目录


🚀 bKash平台概述

市场地位与影响力

bKash是孟加拉国最大的移动金融服务提供商,成立于2011年,由BRAC银行与蚂蚁金服合资成立。截至2024年:

  • 用户规模:超过6000万注册用户,约占孟加拉人口的35%
  • 市场份额:移动支付市场占有率超过70%
  • 交易量:日均处理交易超过2000万笔
  • 覆盖范围:全国64个区县,40万+代理点

业务模式分析

bKash采用代理商模式,通过遍布全国的代理网点提供现金存取服务,形成了完整的数字金融生态:

个人用户服务

  • Send Money(转账)
  • Cash Out(取现)
  • Mobile Recharge(话费充值)
  • Bill Payment(账单缴费)
  • Merchant Payment(商户支付)

商户解决方案

  • Point of Sale(POS支付)
  • Online Payment(在线支付)
  • Bulk Payment(批量支付)
  • Salary Disbursement(工资发放)

📝 商户申请流程

1. 申请前准备

个人商户所需材料

  • 有效身份证件(NID/护照)
  • 银行账户证明
  • 业务证明(网站/APP截图、业务说明书)
  • 预计月交易量说明

企业商户所需材料

  • 营业执照(Trade License)
  • 税务登记证(TIN Certificate)
  • 银行开户许可证
  • 法人身份证明
  • 公司章程
  • 业务许可证(如适用)

2. 申请流程详解

mermaid
graph TD
    A[提交申请材料] --> B[bKash初审]
    B --> C[补充材料]
    C --> D[实地核查]
    D --> E[风控评估]
    E --> F[签署协议]
    F --> G[获得API凭证]
    G --> H[技术集成测试]
    H --> I[正式上线]

时间周期

  • 个人商户:5-10个工作日
  • 企业商户:10-15个工作日
  • 大型企业:15-30个工作日

3. 费率结构

bKash商户费率采用阶梯定价模式:

月交易量 个人商户费率 企业商户费率 备注
< 50万塔卡 1.85% 1.75% 基础费率
50万-200万 1.70% 1.60% 中等规模优惠
200万-500万 1.55% 1.45% 大客户优惠
> 500万塔卡 1.40% 1.30% 最优费率

🏗️ 技术架构解析

系统架构图

┌─────────────────────────────────────────────────────────────┐
│                    bKash Payment Gateway                    │
├─────────────────────────────────────────────────────────────┤
│  Web API Layer  │  Mobile API  │  USSD Gateway │  Agent API │
├─────────────────────────────────────────────────────────────┤
│              Core Payment Processing Engine                 │
├─────────────────────────────────────────────────────────────┤
│   Risk Engine   │   KYC System   │   Notification Service  │
├─────────────────────────────────────────────────────────────┤
│              Database Cluster (MySQL/Redis)               │
├─────────────────────────────────────────────────────────────┤
│          BRAC Bank Core Banking System Integration         │
└─────────────────────────────────────────────────────────────┘

API环境配置

沙盒环境

  • Base URL: https://tokenized.sandbox.bka.sh/v1.2.0-beta
  • 用途: 开发测试
  • 限制: 无真实资金流动

生产环境

  • Base URL: https://tokenized.pay.bka.sh/v1.2.0-beta
  • 用途: 正式交易
  • 要求: 必须通过商户审核

🔌 API接口详解

1. 认证机制 (Grant Token)

javascript
// 获取访问令牌 POST /tokenized/checkout/token/grant
Headers: { "Content-Type": "application/json", "Accept": "application/json", "username": "YOUR_USERNAME", "password": "YOUR_PASSWORD" } Body: { "app_key": "YOUR_APP_KEY", "app_secret": "YOUR_APP_SECRET" } // 响应示例 { "statusCode": "0000", "statusMessage": "Successful", "id_token": "eyJ0eXAiOiJKV1QiLCJhbGc...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGc..." }

2. 创建支付 (Create Payment)

javascript
POST /tokenized/checkout/create
Headers: { "Content-Type": "application/json", "Accept": "application/json", "authorization": "Bearer " + id_token, "x-app-key": "YOUR_APP_KEY" } Body: { "mode": "0011", // Payment mode "payerReference": "CUSTOMER_ID_123", "callbackURL": "https://yoursite.com/callback", "amount": "100.50", "currency": "BDT", "intent": "sale", "merchantInvoiceNumber": "INV_001" } // 响应示例 { "statusCode": "0000", "statusMessage": "Successful", "paymentID": "TR0011234567890", "bkashURL": "https://tokenized.pay.bka.sh/checkout?paymentID=TR0011234567890", "callbackURL": "https://yoursite.com/callback", "successCallbackURL": "https://yoursite.com/success", "failureCallbackURL": "https://yoursite.com/failure", "cancelledCallbackURL": "https://yoursite.com/cancel" }

3. 执行支付 (Execute Payment)

javascript
POST /tokenized/checkout/execute
Headers: { "Content-Type": "application/json", "Accept": "application/json", "authorization": "Bearer " + id_token, "x-app-key": "YOUR_APP_KEY" } Body: { "paymentID": "TR0011234567890" } // 成功响应 { "statusCode": "0000", "statusMessage": "Successful", "paymentID": "TR0011234567890", "trxID": "8M2A59MID7", "transactionStatus": "Completed", "amount": "100.50", "currency": "BDT", "paymentExecuteTime": "2024-01-15T10:30:45", "merchantInvoiceNumber": "INV_001" }

4. 查询支付 (Query Payment)

javascript
POST /tokenized/checkout/payment/status
Headers: { "Content-Type": "application/json", "Accept": "application/json", "authorization": "Bearer " + id_token, "x-app-key": "YOUR_APP_KEY" } Body: { "paymentID": "TR0011234567890" }

💻 代码实战示例

PHP集成示例

php
<?php class BkashPayment { private $app_key; private $app_secret; private $username; private $password; private $base_url; public function __construct($config) { $this->app_key = $config['app_key']; $this->app_secret = $config['app_secret']; $this->username = $config['username']; $this->password = $config['password']; $this->base_url = $config['sandbox'] ? 'https://tokenized.sandbox.bka.sh/v1.2.0-beta' : 'https://tokenized.pay.bka.sh/v1.2.0-beta'; } // 获取Token public function getToken() { $url = $this->base_url . '/tokenized/checkout/token/grant'; $headers = [ 'Content-Type: application/json', 'Accept: application/json', 'username: ' . $this->username, 'password: ' . $this->password ]; $data = [ 'app_key' => $this->app_key, 'app_secret' => $this->app_secret ]; $response = $this->makeRequest($url, $headers, $data); return json_decode($response, true); } // 创建支付 public function createPayment($amount, $invoice_number, $customer_id = '') { $token_response = $this->getToken(); if ($token_response['statusCode'] !== '0000') { throw new Exception('Token获取失败: ' . $token_response['statusMessage']); } $url = $this->base_url . '/tokenized/checkout/create'; $headers = [ 'Content-Type: application/json', 'Accept: application/json', 'authorization: Bearer ' . $token_response['id_token'], 'x-app-key: ' . $this->app_key ]; $data = [ 'mode' => '0011', 'payerReference' => $customer_id, 'callbackURL' => 'https://yoursite.com/bkash/callback', 'amount' => strval($amount), 'currency' => 'BDT', 'intent' => 'sale', 'merchantInvoiceNumber' => $invoice_number ]; $response = $this->makeRequest($url, $headers, $data); return json_decode($response, true); } // 执行支付 public function executePayment($payment_id) { $token_response = $this->getToken(); $url = $this->base_url . '/tokenized/checkout/execute'; $headers = [ 'Content-Type: application/json', 'Accept: application/json', 'authorization: Bearer ' . $token_response['id_token'], 'x-app-key: ' . $this->app_key ]; $data = ['paymentID' => $payment_id]; $response = $this->makeRequest($url, $headers, $data); return json_decode($response, true); } // HTTP请求方法 private function makeRequest($url, $headers, $data) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($data), CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => false ]); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); if (curl_errno($ch)) { throw new Exception('cURL错误: ' . curl_error($ch)); } curl_close($ch); if ($http_code !== 200) { throw new Exception('HTTP错误: ' . $http_code); } return $response; } } // 使用示例 $config = [ 'app_key' => 'your_app_key', 'app_secret' => 'your_app_secret', 'username' => 'your_username', 'password' => 'your_password', 'sandbox' => true // 生产环境设为false ]; $bkash = new BkashPayment($config); try { // 创建支付 $payment = $bkash->createPayment(100.00, 'INV_001', 'CUSTOMER_123'); if ($payment['statusCode'] === '0000') { // 重定向到bKash支付页面 header('Location: ' . $payment['bkashURL']); exit; } else { echo '支付创建失败: ' . $payment['statusMessage']; } } catch (Exception $e) { echo '错误: ' . $e->getMessage(); } ?>

Node.js集成示例

javascript
const axios = require('axios'); class BkashPayment { constructor(config) { this.appKey = config.appKey; this.appSecret = config.appSecret; this.username = config.username; this.password = config.password; this.baseUrl = config.sandbox ? 'https://tokenized.sandbox.bka.sh/v1.2.0-beta' : 'https://tokenized.pay.bka.sh/v1.2.0-beta'; } async getToken() { try { const response = await axios.post( `${this.baseUrl}/tokenized/checkout/token/grant`, { app_key: this.appKey, app_secret: this.appSecret }, { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'username': this.username, 'password': this.password } } ); return response.data; } catch (error) { throw new Error(`Token获取失败: ${error.response?.data?.statusMessage || error.message}`); } } async createPayment(amount, invoiceNumber, customerId = '') { const tokenResponse = await this.getToken(); if (tokenResponse.statusCode !== '0000') { throw new Error(`Token获取失败: ${tokenResponse.statusMessage}`); } try { const response = await axios.post( `${this.baseUrl}/tokenized/checkout/create`, { mode: '0011', payerReference: customerId, callbackURL: 'https://yoursite.com/bkash/callback', amount: amount.toString(), currency: 'BDT', intent: 'sale', merchantInvoiceNumber: invoiceNumber
                }, { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'authorization': `Bearer ${tokenResponse.id_token}`, 'x-app-key': this.appKey } } ); return response.data; } catch (error) { throw new Error(`支付创建失败: ${error.response?.data?.statusMessage || error.message}`); } } async executePayment(paymentId) { const tokenResponse = await this.getToken(); try { const response = await axios.post( `${this.baseUrl}/tokenized/checkout/execute`, { paymentID: paymentId }, { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'authorization': `Bearer ${tokenResponse.id_token}`, 'x-app-key': this.appKey } } ); return response.data; } catch (error) { throw new Error(`支付执行失败: ${error.response?.data?.statusMessage || error.message}`); } } } // 使用示例 const config = { appKey: 'your_app_key', appSecret: 'your_app_secret', username: 'your_username', password: 'your_password', sandbox: true }; const bkash = new BkashPayment(config); // Express.js路由示例
app.post('/create-payment', async (req, res) => { try { const { amount, invoiceNumber, customerId } = req.body; const payment = await bkash.createPayment(amount, invoiceNumber, customerId); if (payment.statusCode === '0000') {
            res.json({ success: true, paymentUrl: payment.bkashURL, paymentId: payment.paymentID }); } else {
            res.status(400).json({ success: false, message: payment.statusMessage }); } } catch (error) {
        res.status(500).json({ success: false, message: error.message }); } });

🔒 安全机制详解

1. API签名验证

bKash使用Bearer Token认证机制,所有API请求都需要在Header中包含有效的访问令牌:

javascript
// Token有效期管理 class TokenManager { constructor() { this.token = null; this.tokenExpiry = null; this.refreshToken = null; } isTokenValid() { return this.token && this.tokenExpiry && new Date() < new Date(this.tokenExpiry); } async getValidToken() { if (!this.isTokenValid()) { await this.refreshAccessToken(); } return this.token; } async refreshAccessToken() { // 刷新token的逻辑 if (this.refreshToken) { // 使用refresh token获取新的access token } else { // 重新获取token const tokenResponse = await this.getToken(); this.token = tokenResponse.id_token; this.tokenExpiry = new Date(Date.now() + tokenResponse.expires_in * 1000); this.refreshToken = tokenResponse.refresh_token; } } }

2. 数据加密传输

所有API通信必须使用HTTPS协议,确保数据在传输过程中的安全性:

javascript
// HTTPS配置示例(Node.js) const https = require('https'); const fs = require('fs'); const options = { key: fs.readFileSync('private-key.pem'), cert: fs.readFileSync('certificate.pem'), // 强制使用TLS 1.2或更高版本 secureProtocol: 'TLSv1_2_method', // 禁用不安全的密码套件 ciphers: [ 'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES128-SHA256', 'ECDHE-RSA-AES256-SHA384' ].join(':'), honorCipherOrder: true };

https.createServer(options, app).listen(443, () => { console.log('HTTPS服务器运行在443端口'); });

3. 回调验证机制

为了确保回调请求的真实性,建议实现以下验证机制:

php
<?php // 回调验证示例 function verifyBkashCallback($postData, $secretKey) { // 1. 验证IP白名单 $allowedIPs = ['103.106.118.10', '103.106.118.11']; // bKash服务器IP $clientIP = $_SERVER['REMOTE_ADDR']; if (!in_array($clientIP, $allowedIPs)) { return false; } // 2. 验证时间戳(防重放攻击) $timestamp = $postData['timestamp'] ?? 0; $currentTime = time(); if (abs($currentTime - $timestamp) > 300) { // 5分钟有效期 return false; } // 3. 验证签名 $expectedSignature = hash_hmac('sha256', json_encode($postData), $secretKey); $receivedSignature = $_SERVER['HTTP_X_BKASH_SIGNATURE'] ?? ''; return hash_equals($expectedSignature, $receivedSignature); } // 处理回调 if ($_SERVER['REQUEST_METHOD'] === 'POST') { $postData = json_decode(file_get_contents('php://input'), true); if (verifyBkashCallback($postData, 'your_secret_key')) { // 回调验证成功,处理业务逻辑 $paymentId = $postData['paymentID']; $trxId = $postData['trxID']; $status = $postData['transactionStatus']; // 更新订单状态 updateOrderStatus($paymentId, $status, $trxId); // 返回成功响应 http_response_code(200); echo json_encode(['status' => 'success']); } else { // 回调验证失败 http_response_code(400); echo json_encode(['status' => 'invalid']); } } ?>

4. 防重放攻击

javascript
// 防重放攻击实现 class ReplayProtection { constructor() { this.usedNonces = new Set(); this.cleanupInterval = setInterval(() => { this.cleanup(); }, 300000); // 每5分钟清理一次 } isValidRequest(nonce, timestamp) { // 检查时间戳 const currentTime = Math.floor(Date.now() / 1000); if (Math.abs(currentTime - timestamp) > 300) { return false; // 超过5分钟的请求拒绝 } // 检查nonce是否已使用 const nonceKey = `${nonce}_${timestamp}`; if (this.usedNonces.has(nonceKey)) { return false; // 重复请求 } // 记录nonce this.usedNonces.add(nonceKey); return true; } cleanup() { // 清理过期的nonce记录 const currentTime = Math.floor(Date.now() / 1000); const expiredTime = currentTime - 300; for (const nonceKey of this.usedNonces) { const timestamp = parseInt(nonceKey.split('_')[1]); if (timestamp < expiredTime) { this.usedNonces.delete(nonceKey); } } } }

🐛 常见问题解决

1. Token过期问题

问题现象:API返回401 Unauthorized错误

解决方案

javascript
// 自动重试机制 async function makeRequestWithRetry(apiCall, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await apiCall(); } catch (error) { if (error.response?.status === 401 && i < maxRetries - 1) { // Token过期,刷新后重试 await tokenManager.refreshAccessToken(); continue; } throw error; } } }

2. 网络超时处理

问题现象:请求超时或连接失败

解决方案

javascript
// 超时和重试配置 const axiosConfig = { timeout: 30000, // 30秒超时 retry: 3, retryDelay: 1000, retryCondition: (error) => { return error.code === 'ECONNABORTED' || (error.response && error.response.status >= 500); } }; // 指数退避重试 async function exponentialBackoffRetry(fn, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (i === maxRetries - 1) throw error; const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s await new Promise(resolve => setTimeout(resolve, delay)); } } }

3. 支付状态不一致

问题现象:本地订单状态与bKash状态不匹配

解决方案

php
<?php // 定时对账任务 function reconcilePayments() { $pendingPayments = getPendingPayments(); // 获取待确认支付 foreach ($pendingPayments as $payment) { $paymentId = $payment['payment_id']; // 查询bKash支付状态 $bkashStatus = queryBkashPaymentStatus($paymentId); if ($bkashStatus['statusCode'] === '0000') { $transactionStatus = $bkashStatus['transactionStatus']; // 更新本地状态 updateLocalPaymentStatus($paymentId, $transactionStatus); // 记录对账日志 logReconciliation($paymentId, $transactionStatus); } // 避免API频率限制 sleep(1); } } // 每30分钟执行一次对账 // 在crontab中添加: */30 * * * * php /path/to/reconcile.php ?>

4. 金额精度问题

问题现象:小数点金额处理错误

解决方案

javascript
// 金额处理工具类 class AmountUtils { static formatAmount(amount) { // 确保金额为两位小数 return parseFloat(amount).toFixed(2); } static validateAmount(amount) { const numAmount = parseFloat(amount); // 检查是否为有效数字 if (isNaN(numAmount) || numAmount <= 0) { throw new Error('无效金额'); } // 检查最小金额限制(bKash最小支付1塔卡) if (numAmount < 1) { throw new Error('金额不能少于1塔卡'); } // 检查最大金额限制 if (numAmount > 25000) { throw new Error('单笔支付不能超过25000塔卡'); } return this.formatAmount(numAmount); } static convertToPaisa(amount) { // 将塔卡转换为派沙(1塔卡=100派沙) return Math.round(parseFloat(amount) * 100); } }

🎯 最佳实践建议

1. 错误处理策略

javascript
// 统一错误处理 class BkashErrorHandler { static handleError(error, context = '') { const errorInfo = { timestamp: new Date().toISOString(), context: context, error: { message: error.message, code: error.code || 'UNKNOWN', statusCode: error.response?.status, responseData: error.response?.data
            } }; // 记录错误日志 console.error('bKash API Error:', JSON.stringify(errorInfo, null, 2)); // 根据错误类型返回用户友好的错误信息 switch (error.response?.status) { case 400: return '请求参数错误,请检查支付信息'; case 401: return '认证失败,请稍后重试'; case 403: return '权限不足,请联系客服'; case 429: return '请求过于频繁,请稍后重试'; case 500: return 'bKash服务暂时不可用,请稍后重试'; default: return '支付处理失败,请稍后重试或联系客服'; } } static isRetryableError(error) { const retryableStatuses = [408, 429, 500, 502, 503, 504]; return retryableStatuses.includes(error.response?.status) ||
               error.code === 'ECONNABORTED' ||
               error.code === 'ENOTFOUND'; } }

2. 日志记录规范

javascript
// 支付日志记录 class PaymentLogger { static logPaymentStart(invoiceNumber, amount, customerId) { const logData = { level: 'INFO', action: 'PAYMENT_START', timestamp: new Date().toISOString(), data: {
                invoiceNumber,
                amount,
                customerId, sessionId: this.generateSessionId() } }; this.writeLog(logData); return logData.data.sessionId; } static logPaymentCreated(sessionId, paymentId, bkashUrl) { const logData = { level: 'INFO', action: 'PAYMENT_CREATED', timestamp: new Date().toISOString(),
            sessionId, data: {
                paymentId,
                bkashUrl
            } }; this.writeLog(logData); } static logPaymentCompleted(sessionId, paymentId, trxId, amount) { const logData = { level: 'INFO', action: 'PAYMENT_COMPLETED', timestamp: new Date().toISOString(),
            sessionId, data: {
                paymentId,
                trxId,
                amount, completedAt: new Date().toISOString() } }; this.writeLog(logData); } static logPaymentError(sessionId, error, context) { const logData = { level: 'ERROR', action: 'PAYMENT_ERROR', timestamp: new Date().toISOString(),
            sessionId, data: { error: error.message,
                context, stack: error.stack } }; this.writeLog(logData); } static writeLog(logData) { // 写入日志文件或发送到日志服务 console.log(JSON.stringify(logData)); // 生产环境中建议使用专业的日志服务 // 如 Winston, Bunyan 或云端日志服务 } static generateSessionId() { return 'PAY_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } }

3. 性能优化建议

javascript
// 连接池管理 const https = require('https'); const agent = new https.Agent({ keepAlive: true, keepAliveMsecs: 30000, maxSockets: 50, maxFreeSockets: 10, timeout: 30000 }); // API请求优化 class OptimizedBkashClient { constructor(config) { this.config = config; this.tokenCache = null; this.tokenExpiry = null; // 创建优化的axios实例 this.httpClient = axios.create({ httpsAgent: agent, timeout: 30000, headers: { 'Connection': 'keep-alive', 'Accept-Encoding': 'gzip, deflate' } }); // 添加请求拦截器 this.httpClient.interceptors.request.use(this.requestInterceptor.bind(this)); this.httpClient.interceptors.response.use( this.responseInterceptor.bind(this), this.errorInterceptor.bind(this) ); } requestInterceptor(config) { // 添加请求追踪ID
        config.headers['X-Request-ID'] = this.generateRequestId(); // 记录请求开始时间
        config.metadata = { startTime: Date.now() }; return config; } responseInterceptor(response) { // 计算响应时间 const duration = Date.now() - response.config.metadata.startTime; console.log(`API请求耗时: ${duration}ms`); return response; } errorInterceptor(error) { if (error.config) { const duration = Date.now() - error.config.metadata.startTime; console.error(`API请求失败,耗时: ${duration}ms`); } return Promise.reject(error); } generateRequestId() { return 'req_' + Date.now() + '_' + Math.random().toString(36).substr(2, 8); } }

4. 缓存策略

javascript
// Redis缓存实现 const redis = require('redis'); const client = redis.createClient({ host: 'localhost', port: 6379, db: 0 }); class BkashCache { static async cacheToken(token, expiresIn) { const key = 'bkash:token'; const ttl = expiresIn - 60; // 提前1分钟过期,确保安全 await client.setex(key, ttl, JSON.stringify(token)); } static async getToken() { const key = 'bkash:token'; const cached = await client.get(key); return cached ? JSON.parse(cached) : null; } static async cachePaymentStatus(paymentId, status, ttl = 300) { const key = `bkash:payment:${paymentId}`; await client.setex(key, ttl, JSON.stringify(status)); } static async getPaymentStatus(paymentId) { const key = `bkash:payment:${paymentId}`; const cached = await client.get(key); return cached ? JSON.parse(cached) : null; } static async invalidatePaymentCache(paymentId) { const key = `bkash:payment:${paymentId}`; await client.del(key); } }

5. 监控和告警

javascript
// 监控指标收集 class PaymentMetrics { constructor() { this.metrics = { totalPayments: 0, successfulPayments: 0, failedPayments: 0, averageResponseTime: 0, errorRates: {}, lastReset: Date.now() }; } recordPaymentAttempt() { this.metrics.totalPayments++; } recordPaymentSuccess(responseTime) { this.metrics.successfulPayments++; this.updateAverageResponseTime(responseTime); } recordPaymentFailure(errorCode) { this.metrics.failedPayments++; this.metrics.errorRates[errorCode] = (this.metrics.errorRates[errorCode] || 0) + 1; // 检查是否需要发送告警 this.checkAlerts(); } updateAverageResponseTime(responseTime) { const total = this.metrics.successfulPayments; const current = this.metrics.averageResponseTime; this.metrics.averageResponseTime = ((current * (total - 1)) + responseTime) / total; } checkAlerts() { const failureRate = this.getFailureRate(); // 失败率超过10%时发送告警 if (failureRate > 0.1) { this.sendAlert({ type: 'HIGH_FAILURE_RATE', rate: failureRate, message: `bKash支付失败率过高: ${(failureRate * 100).toFixed(2)}%` }); } // 响应时间超过5秒时发送告警 if (this.metrics.averageResponseTime > 5000) { this.sendAlert({ type: 'SLOW_RESPONSE', responseTime: this.metrics.averageResponseTime, message: `bKash API响应时间过慢: ${this.metrics.averageResponseTime}ms` }); } } getFailureRate() { const total = this.metrics.totalPayments; return total > 0 ? this.metrics.failedPayments / total : 0; } getSuccessRate() { const total = this.metrics.totalPayments; return total > 0 ? this.metrics.successfulPayments / total : 0; } sendAlert(alert) { // 发送告警到监控系统或通知渠道 console.error('🚨 bKash支付告警:', alert); // 可以集成钉钉、微信、邮件等通知方式 // this.sendDingTalkAlert(alert); // this.sendEmailAlert(alert); } getMetrics() { return { ...this.metrics, successRate: this.getSuccessRate(), failureRate: this.getFailureRate(), uptime: Date.now() - this.metrics.lastReset }; } reset() { this.metrics = { totalPayments: 0, successfulPayments: 0, failedPayments: 0, averageResponseTime: 0, errorRates: {}, lastReset: Date.now() }; } } // 全局监控实例 const paymentMetrics = new PaymentMetrics(); // 定期重置统计(每小时) setInterval(() => { console.log('当前支付指标:', paymentMetrics.getMetrics());
    paymentMetrics.reset(); }, 3600000);

6. 数据库设计建议

sql
-- 支付记录表 CREATE TABLE bkash_payments (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    invoice_number VARCHAR(50) NOT NULL UNIQUE,
    payment_id VARCHAR(50) UNIQUE,
    trx_id VARCHAR(20),
    customer_id VARCHAR(50),
    amount DECIMAL(10,2) NOT NULL,
    currency VARCHAR(3) DEFAULT 'BDT', status ENUM('PENDING', 'CREATED', 'COMPLETED', 'FAILED', 'CANCELLED') DEFAULT 'PENDING',
    bkash_url TEXT,
    callback_data JSON,
    error_message TEXT,
    session_id VARCHAR(50),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    completed_at TIMESTAMP NULL, INDEX idx_invoice_number (invoice_number), INDEX idx_payment_id (payment_id), INDEX idx_trx_id (trx_id), INDEX idx_customer_id (customer_id), INDEX idx_status (status), INDEX idx_created_at (created_at) ); -- 支付日志表 CREATE TABLE bkash_payment_logs (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    payment_id VARCHAR(50),
    session_id VARCHAR(50), action VARCHAR(50) NOT NULL, level ENUM('INFO', 'WARN', 'ERROR') DEFAULT 'INFO',
    message TEXT,
    request_data JSON,
    response_data JSON,
    error_data JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_payment_id (payment_id), INDEX idx_session_id (session_id), INDEX idx_action (action), INDEX idx_level (level), INDEX idx_created_at (created_at) ); -- 对账记录表 CREATE TABLE bkash_reconciliation (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    payment_id VARCHAR(50),
    local_status VARCHAR(20),
    bkash_status VARCHAR(20),
    is_matched BOOLEAN DEFAULT FALSE,
    discrepancy_reason TEXT,
    reconciled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_payment_id (payment_id), INDEX idx_is_matched (is_matched), INDEX idx_reconciled_at (reconciled_at) );

7. 部署和运维建议

Docker部署示例

dockerfile
# Dockerfile FROM node:16-alpine WORKDIR /app # 复制package文件 COPY package*.json ./ # 安装依赖 RUN npm ci --only=production # 复制源码 COPY . . # 创建非root用户 RUN addgroup -g 1001 -S nodejs && \     adduser -S nextjs -u 1001 # 设置权限 RUN chown -R nextjs:nodejs /app USER nextjs # 暴露端口 EXPOSE 3000 # 健康检查 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:3000/health || exit 1 # 启动应用 CMD ["npm", "start"]

Docker Compose配置

yaml
version: '3.8' services: bkash-payment-api: build: .
    ports: - "3000:3000" environment: - NODE_ENV=production
      - BKASH_APP_KEY=${BKASH_APP_KEY} - BKASH_APP_SECRET=${BKASH_APP_SECRET} - BKASH_USERNAME=${BKASH_USERNAME} - BKASH_PASSWORD=${BKASH_PASSWORD} - REDIS_URL=redis://redis:6379 - DB_HOST=mysql
      - DB_USER=${DB_USER} - DB_PASSWORD=${DB_PASSWORD} - DB_NAME=${DB_NAME} depends_on: - redis
      - mysql
    restart: unless-stopped
    
  redis: image: redis:7-alpine
    ports: - "6379:6379" volumes: - redis_data:/data
    restart: unless-stopped
    
  mysql: image: mysql:8.0 ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} - MYSQL_DATABASE=${DB_NAME} - MYSQL_USER=${DB_USER} - MYSQL_PASSWORD=${DB_PASSWORD} volumes: - mysql_data:/var/lib/mysql
    restart: unless-stopped

volumes: redis_data:
  mysql_data:

Nginx反向代理配置

nginx
# /etc/nginx/sites-available/bkash-payment server { listen 80; server_name your-domain.com; # 重定向到HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name your-domain.com; # SSL配置 ssl_certificate /path/to/certificate.crt; ssl_certificate_key /path/to/private.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; # 安全头 add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains"; # API路由 location /api/ { proxy_pass http://localhost:3000/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; # 超时设置 proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; } # 健康检查 location /health { proxy_pass http://localhost:3000/health; access_log off; } # 限流配置 location /api/payment/ { limit_req zone=payment burst=10 nodelay; proxy_pass http://localhost:3000/api/payment/; } } # 限流配置 http { limit_req_zone $binary_remote_addr zone=payment:10m rate=30r/m; }

📚 相关资源链接

官方文档

技术社区

测试工具


📞 技术支持

如果您在集成过程中遇到任何问题,可以通过以下方式获得支持:


本指南由孟加拉支付系统专家团队编写,定期更新以确保信息的准确性和时效性。最后更新时间:2024年12月

Share This Story, Choose Your Platform!