
本文详解如何通过 MerchantRequestID 和 CheckoutRequestID 实现支付交易与用户的精准绑定,规避因 Safaricom 数据最小化政策导致的手机号脱敏问题,确保业务系统可追溯、可审计。
本文详解如何通过 merchantrequestid 和 checkoutrequestid 实现支付交易与用户的精准绑定,规避因 safaricom 数据最小化政策导致的手机号脱敏问题,确保业务系统可追溯、可审计。
在使用 MPESA Express(STK Push)API v1 接收客户付款时,开发者常依赖回调中返回的 PhoneNumber 字段来识别用户。但随着 Safaricom 实施数据最小化(Data Minimisation)策略,回调响应中的 PhoneNumber 值可能被截断或脱敏(如仅返回末4位),无法再作为唯一、可靠的用户标识依据。此时,若仍试图通过 AccountReference 或 TransactionDesc 在回调中“找回”用户信息,会发现这些字段不会原样回传至回调响应体中——这是 MPESA API 的明确设计限制:AccountReference 仅用于账单显示(如 M-Pesa 账单界面展示的“收款方备注”),不参与回调数据映射。
✅ 正确且官方推荐的解决方案是:利用请求阶段生成的唯一标识符,在发起请求时持久化存储,并在回调中通过匹配该标识完成交易归属判定。核心依赖两个关键字段:
-
MerchantRequestID:由商户系统生成的全局唯一请求 ID(建议使用 UUID 或时间戳+随机数),用于标识本次支付请求的业务上下文; -
CheckoutRequestID:由 Safaricom 生成的平台级唯一交易 ID,在请求成功后返回,是回调中唯一可稳定匹配的 Safaricom 端标识。
✅ 实施步骤(三步闭环)
1. 发起 STK Push 请求时:生成并存储映射关系
在调用 /stkpush/v1/processrequest 前,务必:
- 生成唯一的
MerchantRequestID(如uniqid('stk_', true)或bin2hex(random_bytes(16))); - 将该 ID、用户 ID(如数据库
user_id)、手机号、订单号等关键业务信息写入本地数据库临时表(如mpesa_stk_requests); - 将
MerchantRequestID传入请求体(SDK 或 cURL 均支持); -
注意:
AccountReference可设为业务标识(如用户昵称/订单号),但仅作前端展示,不可依赖其回传。
$merchantRequestId = bin2hex(random_bytes(16)); // 示例:生成唯一 ID
$checkoutRequestId = null;
// 存储请求上下文(关键!)
$sql = "INSERT INTO mpesa_stk_requests (
merchant_request_id,
user_id,
phone_number,
amount,
order_ref,
status,
created_at
) VALUES (?, ?, ?, ?, ?, 'pending', NOW())";
$stmt = $pdo->prepare($sql);
$stmt->execute([$merchantRequestId, $userId, $phoneNumber, $amount, $orderRef]);
// 构建 STK 请求体
$postData = [
"BusinessShortCode" => "174379",
"Password" => $password,
"Timestamp" => $timestamp,
"TransactionType" => "CustomerPayBillOnline",
"Amount" => $amount,
"PartyA" => $phoneNumber,
"PartyB" => "174379",
"PhoneNumber" => $phoneNumber,
"CallBackURL" => "https://yourdomain.com/callback/stk",
"AccountReference" => $orderRef, // 仅显示用途
"TransactionDesc" => "Payment for Order #{$orderRef}",
"MerchantRequestID" => $merchantRequestId // ← 必须显式传递!
];
$response = sendCurlPost($endpoint, $postData, $accessToken);
$result = json_decode($response, true);
if ($result['ResponseCode'] === '0') {
$checkoutRequestId = $result['CheckoutRequestID'];
// 更新本地记录,补充 Safaricom 返回的 ID
$updateSql = "UPDATE mpesa_stk_requests
SET checkout_request_id = ?, status = 'sent'
WHERE merchant_request_id = ?";
$pdo->prepare($updateSql)->execute([$checkoutRequestId, $merchantRequestId]);
}
2. 处理回调(Callback URL):通过 CheckoutRequestID 精准定位
Safaricom 回调响应的 Body.stkCallback 中必定包含 CheckoutRequestID,且该值与你数据库中存储的完全一致。这是你关联交易的黄金钥匙。
$content = file_get_contents('php://input');
$data = json_decode($content, true);
$checkoutId = $data['Body']['stkCallback']['CheckoutRequestID'] ?? null;
$resultCode = $data['Body']['stkCallback']['ResultCode'] ?? null;
if (!$checkoutId) {
http_response_code(400);
exit('Invalid callback: missing CheckoutRequestID');
}
// 查询本地数据库中匹配的请求记录
$stmt = $pdo->prepare("SELECT * FROM mpesa_stk_requests WHERE checkout_request_id = ?");
$stmt->execute([$checkoutId]);
$request = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$request) {
error_log("Callback mismatch: CheckoutRequestID {$checkoutId} not found in DB");
http_response_code(404);
exit();
}
// ✅ 此时 request['user_id'] 即为本次交易的真实用户!
$userId = $request['user_id'];
$receiptNo = null;
// 解析回调元数据获取收据号等信息
if (isset($data['Body']['stkCallback']['CallbackMetadata']['Item'])) {
foreach ($data['Body']['stkCallback']['CallbackMetadata']['Item'] as $item) {
if ($item['Name'] === 'MpesaReceiptNumber') {
$receiptNo = $item['Value'];
break;
}
}
}
// 更新交易状态 & 关联用户操作
$updateSql = "UPDATE mpesa_stk_requests
SET result_code = ?,
result_desc = ?,
mpesa_receipt = ?,
status = ?,
updated_at = NOW()
WHERE checkout_request_id = ?";
$pdo->prepare($updateSql)->execute([
$resultCode,
$data['Body']['stkCallback']['ResultDesc'] ?? '',
$receiptNo,
($resultCode == '0') ? 'success' : 'failed',
$checkoutId
]);
// ✅ 关键业务动作:例如更新用户订单状态、发放服务权限等
if ($resultCode == '0' && $receiptNo) {
updateOrderStatus($request['order_ref'], 'paid', $receiptNo, $userId);
}
3. 注意事项与最佳实践
- ?
MerchantRequestID必须全局唯一且不可重用:重复使用会导致数据错乱,建议用加密随机数(非自增ID); - ⏱️ 回调可能延迟或重试:Safaricom 可能多次推送相同
CheckoutRequestID,你的回调逻辑必须幂等(如先查再更新,避免重复扣费/发券); - ? 日志必做:记录每次回调原始 JSON、处理结果、SQL 执行状态,便于审计与故障排查;
- ? 环境隔离:沙箱(sandbox)与生产(live)环境的
BusinessShortCode、passkey、证书均不同,切勿混用; - ?️ 安全加固:验证回调请求来源(检查
HTTP_X_FORWARDED_FOR或 Safaricom IP 白名单),并对敏感字段(如手机号)加密存储。
通过这套基于 MerchantRequestID + CheckoutRequestID 的双ID映射机制,你完全摆脱了对明文手机号的依赖,既符合数据合规要求,又保障了交易归属的 100% 准确性。这是当前 MPESA 生态下最健壮、最被广泛验证的用户关联方案。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










