实战教学:配置bKash回调接口的步骤

I

bKash回调接口配置实战指南

准备工作

  1. 确保已拥有bKash商户账户并开通API权限
  2. 获取以下关键信息:
    • Merchant ID
    • Merchant Wallet Number (用户名)
    • API Key
    • API Secret

服务器端配置步骤

1. 创建接收回调的URL端点

// PHP示例代码(可根据实际语言调整)
<?php
header('Content-Type: application/json');

// bKash发送的数据是JSON格式的POST请求体
$json = file_get_contents('php://input');
$data = json_decode($json, true);

// TODO: 验证签名和业务逻辑处理...

http_response_code(200);
echo json_encode(['status' => 'success']);
?>

2. SSL证书配置(必须HTTPS)

  • bKash只支持HTTPS回调,确保你的服务器有有效的SSL证书

3. IP白名单设置(可选但推荐)

将bKash官方IP加入防火墙白名单:

103.102.43.0/24 
103.76.52.0/22
45\.249\.212\.[0-9]{1,3}

bKash商家后台配置步骤

  1. 登录 bKash商户门户
  2. 导航至 "API Settings" > "Callback URL"
    3.填写字段:

    成功支付回调URL: https://yourdomain.com/bkash/success_callback  
    失败支付回调URL: https://yourdomain.com/bkash/fail_callback
    退款通知URL: https://yourdomain.com/bkash/refund_callback
    定期付款通知URL(如适用): https://yourdomain.com/bkash/recurring_callback

4.保存并测试
5.等待审核(通常需要24小时)

Java Spring Boot示例实现

@RestController @RequestMapping("/api/payment")
public class BkashCallbackController {

private static final Logger logger = LoggerFactory.getLogger(BkathCallbackController.class);

@PostMapping("/callback")
public ResponseEntity<Map<String,String>> handleCallback(
@RequestBody Map<String,Object> payload,
HttpServletRequest request) {

// Step1:验证IP来源是否来自bkath官方IP范围

// Step2:验证签名(Signature或X-BKA-SIGNATURE头)

// Step3:处理业务逻辑(订单状态更新等)

logger.info("Received bkath callback:"+payload.toString());

return ResponseEntity.ok(Map.of("status","success"));
}
}

Python Flask示例实现

from flask import Flask,request,jsonify 

app=Flask(__name__)

@app.route('/bkath/callbacks',methods=['POST'])
def payment_notification():
data=request.get_json()
print(f"Received callback:{data}")
#TODO添加验签和业务处理

return jsonify({"status":"received"}),200

if __name__=='__main__':
app.run(ssl_context='adhoc') #生产环境使用真实证书替代adhoc模式 ```

常见问题排查(Q&A)

Q:为什么我的回调没有被触发?
A:-检查网络可达性(bKa sh能否访问你的服务)-确认没有防火墙拦截-确认使用了HTTPS而非HTTP

Q:如何处理重复通知?
A:-通过paymentID去重记录每个交易的状态变更历史

Q:如何测试沙箱环境的回调解调?
A:-使用[开发者沙盒](https:/developer.bka.sh/)模拟各种场景

bKash回调接口配置进阶指南(续)

4. 签名验证实现细节

bKash使用SHA256算法生成签名,以下是Java验证示例:

public boolean verifySignature(String payload, String receivedSignature, String secretKey) {
try {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);

byte[] signatureBytes = sha256_HMAC.doFinal(payload.getBytes());
String computedSignature = Base64.getEncoder().encodeToString(signatureBytes);

return computedSignature.equals(receivedSignature);
} catch (Exception e) {
logger.error("签名验证失败", e);
return false;
}
}

5. 回调数据解析与处理

典型成功支付回调数据结构:

{
"paymentID": "TR0011X8",
"createTime": "2023-05-20T12:00:00 GMT+6",
"updateTime": "2023-05-20T12:02:30 GMT+6",
"trxID": "7X9A2B3C4D",
transactionStatus":"Completed",
amount":"500.50",
currency":"BDT" ,
intent:"sale" ,
merchantInvoiceNumber:"INV-10001"
}

建议数据库存储字段至少包含:

payment_id | trx_id | status | amount | currency | invoice_no 
callback_time | processed_flag | verification_count

Node.js完整示例实现

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

// Middleware验证bkath签名
function verifyBkashSig(req, res, next){
const bkashSig=req.headers['x-bkash-signature'];
const payload=JSON.stringify(req.body);

const hmac=crypto.createHmac('sha256', process.env.BKASH_SECRET_KEY)
.update(payload)
.digest('base64');

if(hmac===bkashSig){
next(); //验签通过继续处理流程
}else{
res.status(403).send({error:"Invalid Signature"});
}
}

app.post('/webhook/bkath',verifyBkashSig,(req,res)=>{
console.log("Valid callback received:",req.body);

//业务逻辑示例:更新订单状态为已支付

res.status(200).json({status:"processed"});
});

app.listen(4430,()=>console.log("Callback server running"));

生产环境关键注意事项

  1. 幂等性设计

    • bKa sh可能重发相同通知(网络超时等情况)
    • trxID应作为唯一键避免重复处理
  2. 异步处理机制

# Python Celery示例(Django/Flask通用)
@app.task(bind=True,max_retries=3)
def process_bkath_callback(self,callback_data):
try:
order=Order.objects.get(invoice_no=callback_data['merchantInvoiceNumber'])
if not order.is_paid:
order.mark_as_paid()
except Exception as exc:
self.retry(exc=exc) #自动重试机制

@shared_task(queue='high_priority')
def log_failed_callback(raw_data):... #专门记录失败的回调信息 ```

3.监控告警系统集成:建议添加以下监控点:

监控项|阈值|响应措施|
---|---|---
未处理的回调积压数|>10条|立即人工检查|
平均响应时间|>800ms|扩容服务器|
5xx错误率|>0.1%|触发告警|

4.合规性要求:根据孟加拉国央行规定必须保留原始通知数据至少5年


沙箱测试全流程

1)获取测试凭证:[Sandbox Credentials Generator](https://developer.bka.sh/test-creds)

2)使用Postman模拟流程:

步骤①:获取授权Token

POST /tokenized/checkout/token/grant HTTP/1.1
Headers:{username:,password:,} Body:{app_key:,app_secret:}

步骤②:发起模拟交易请求  

步骤③:在商家后台配置`ngrok`临时HTTPS地址进行接收测试

推荐测试用例场景包括但不限于以下情况:

Test Case ID Description Expected Result
TC-BKH-001 正常支付完成 收到success_callback且验签通过
TC-BKH-002 部分退款操作 refund_callback触发且金额匹配
TC-BKH-003 恶意伪造请求 返回403并记录安全日志

需要更详细的哪个部分的实现?我可以提供特定语言或框架的深度代码示范。

bKash回调接口深度优化与安全实践

6. 高级安全防护方案

6.1 多层防御机制

# Python Django中间件示例
class BkashSecurityMiddleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
# 第一层:IP白名单验证
client_ip = request.META.get('REMOTE_ADDR')
if not self._is_valid_ip(client_ip):
return HttpResponseForbidden()

# 第二层:请求频率限制(10次/分钟)
rate_limit_key = f"bkash_ratelimit_{client_ip}"
count = cache.get(rate_limit_key, default=0)
if count > 10:
return HttpResponseTooManyRequests()

response = self.get_response(request)

第三层:响应数据脱敏处理(如需返回敏感信息)
if isinstance(response.data,dict):
response.data=self._sanitize_data(response.data)

return response

def _is_valid_ip(self, ip):
allowed_subnets=['103.102.43.0/24','45.249.212.0/23']
for subnet in allowed_subnets:
if ipaddress.ip_address(ip) in ipaddress.ip_network(subnet):
return True
return False

sanitize_fields=['accountNo','nid']
def _sanitize_data(self,data:dict)->dict:
for field in self.sanitize_fields:
data[field]=data[field][-4:].rjust(len(data[field]),'*')
return data

6.2 JWT增强验证(适用于企业版API)

// Java JWT验签增强示例 (Spring Security)
public class BkashJwtFilter extends OncePerRequestFilter {

@Override protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) {

String authHeader=req.getHeader("Authorization");
if(authHeader==null||!authHeader.startsWith("Bearer ")){
sendError(res,"Missing JWT token");return;
}

try{
DecodedJWT jwt=JWT.decode(authHeader.substring(7));

//额外校验发行者是否为bKash官方
Algorithm algorithm=Algorithm.HMAC256(config.getBkashApiSecret());
algorithm verify(jwt);

检查自定义声明是否符合预期,例如:
assert jwt claim merchantId equals config merchantId;

}catch(JWTVerificationException e){
sendError(res,"Invalid token:"+e.getMessage());return;
}

chain.doFilter(req res);
}
}

7. 高可用架构设计

7.1 集群部署方案

                       [Load Balancer]
/ | \
/ | \
[Nginx Pod]——[Redis Cluster]——[App Pods]——[DB Replica Set]
↑ ↓
[Rate Limiting] [Circuit Breaker]

关键配置项:

组件 配置要点
Nginx limit_req_zone $binary_remote_addr zone=bkath_callback:10m rate=100r/m;
Redis 设置持久化AOF模式并启用TLS传输加密
应用节点 实现优雅停机机制确保正在处理的回调不中断

7.2 灾难恢复流程

1)当主数据中心不可用时自动切换DNS至备用区域
2)使用WAL日志进行数据重建的步骤:
bash pg wal replay --recovery-target-time="2023-06-01T12:00:00Z"

3)事后验证清单:
✓比较切换前后最后一个成功处理的trxID是否连续
✓抽样检查金额一致性
✓确认监控系统无异常告警

8. 合规审计准备

需要准备的文档材料包括:

文档类型 内容要求 更新频率
PCI DSS SAQ AEP证明 第三方支付处理器合规证明 年审
交易流水存档 包含原始请求/响应报文、时间戳、IP等 永久保存
渗透测试报告 由认证机构出具的Web应用安全扫描结果每季度

审计重点关注的5个领域:

①签名算法实现是否正确(必须使用HMAC-SHA256而非MD5等弱算法)
②敏感信息是否在日志中正确脱敏
③密钥轮换策略是否符合规范(建议每90天更换API Secret)
④错误率监控是否覆盖所有5xx状态码
⑤员工访问权限是否遵循最小特权原则


Q&A补充解答

Q:如何处理时区差异问题?
A: bKa sh所有时间字段均使用孟加拉标准时区(GMT+6),推荐解决方案:

数据库存储统一UTC时间并在应用层转换:

 created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
bkath_time VARCHAR(25) --原始带时区的时间字符串如"2023-06-20T15:30:45 GMT+6"
); ```

前端显示根据用户位置动态转换的JavaScript代码示例:
```javascript function formatBkathTime(bdTimeStr){
const options={timeZone:'Asia/Dhaka',year:'numeric',month:'short'...};
new Date(bdTimeStr).toLocaleString('en-US',options);} ```

是否需要针对您的具体技术栈提供更多实现细节?例如.NET Core或Go语言的特定实现方案。