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!