如何在 MPESA STK Push API v1 中可靠关联支付交易与用户

千瑶姑娘_2678

千瑶姑娘_2678

2026-09-06

899人浏览

原创

如何在 MPESA STK Push API v1 中可靠关联支付交易与用户

本文详解如何通过 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应用能力赋能!

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
php文件怎么打开
php文件怎么打开

打开php文件步骤:1、选择文本编辑器;2、在选择的文本编辑器中,创建一个新的文件,并将其保存为.php文件;3、在创建的PHP文件中,编写PHP代码;4、要在本地计算机上运行PHP文件,需要设置一个服务器环境;5、安装服务器环境后,需要将PHP文件放入服务器目录中;6、一旦将PHP文件放入服务器目录中,就可以通过浏览器来运行它。

2023.09.01

9684

6

php怎么取出数组的前几个元素
php怎么取出数组的前几个元素

取出php数组的前几个元素的方法有使用array_slice()函数、使用array_splice()函数、使用循环遍历、使用array_slice()函数和array_values()函数等。本专题为大家提供php数组相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.11

5801

5

php反序列化失败怎么办
php反序列化失败怎么办

php反序列化失败的解决办法检查序列化数据。检查类定义、检查错误日志、更新PHP版本和应用安全措施等。本专题为大家提供php反序列化相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.11

2055

5

php怎么连接mssql数据库
php怎么连接mssql数据库

连接方法:1、通过mssql_系列函数;2、通过sqlsrv_系列函数;3、通过odbc方式连接;4、通过PDO方式;5、通过COM方式连接。想了解php怎么连接mssql数据库的详细内容,可以访问下面的文章。

2023.10.23

3628

4

php连接mssql数据库的方法
php连接mssql数据库的方法

php连接mssql数据库的方法有使用PHP的MSSQL扩展、使用PDO等。想了解更多php连接mssql数据库相关内容,可以阅读本专题下面的文章。

2023.10.23

4314

6

html怎么上传
html怎么上传

html通过使用HTML表单、JavaScript和PHP上传。更多关于html的问题详细请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.03

3391

9

PHP出现乱码怎么解决
PHP出现乱码怎么解决

PHP出现乱码可以通过修改PHP文件头部的字符编码设置、检查PHP文件的编码格式、检查数据库连接设置和检查HTML页面的字符编码设置来解决。更多关于php乱码的问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.09

4817

8

php文件怎么在手机上打开
php文件怎么在手机上打开

php文件在手机上打开需要在手机上搭建一个能够运行php的服务器环境,并将php文件上传到服务器上。再在手机上的浏览器中输入服务器的IP地址或域名,加上php文件的路径,即可打开php文件并查看其内容。更多关于php相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.13

3762

8

sprintf函数用法详解
sprintf函数用法详解

sprintf函数的用法:1、格式化字符串;2、指定输出宽度和精度;3、返回值。更多关于sprintf函数用法详解的内容,大家可以阅读下面的文章。

2023.11.27

11762

4

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.6万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.4万人学习