如何获取并处理M-PESA API发送到callback URL的回调数据
核心问题原因
你当前代码的所有逻辑都包裹在if (isset($_POST['submit']))判断下,而M-PESA的回调请求是平台服务器主动发起的,不会携带submit表单参数,同时回调发送的是JSON格式数据,PHP默认的$_POST超全局变量无法直接解析这类请求,所以你的代码完全不会触发回调处理逻辑。
具体修改方案
1. 拆分支付发起和回调处理逻辑
将transact.php的逻辑拆成两部分:一部分处理用户表单提交发起STK推送的请求,另一部分单独处理M-PESA的回调请求,不要嵌套判断。
2. 接收解析JSON回调数据
M-PESA回调的请求体是原始JSON,需要从php://input读取后解析,修改后的完整代码参考如下:
<?php // 处理M-PESA回调逻辑 $callbackData = file_get_contents('php://input'); if (!empty($callbackData)) { // 可选:将回调数据写入日志方便排查,需给日志文件设置可写权限 file_put_contents('mpesa_callback.log', date('[Y-m-d H:i:s] ') . $callbackData . PHP_EOL, FILE_APPEND); $callbackObj = json_decode($callbackData); $stkCallback = $callbackObj->Body->stkCallback; $resultCode = $stkCallback->ResultCode; // 根据ResultCode处理不同业务逻辑 if ($resultCode == 0) { // 支付成功逻辑 $merchantRequestId = $stkCallback->MerchantRequestID; $checkoutRequestId = $stkCallback->CheckoutRequestID; // 遍历获取回调元数据 $metadata = []; foreach ($stkCallback->CallbackMetadata->Item as $item) { $metadata[$item->Name] = $item->Value; } $amount = $metadata['Amount']; $receiptNumber = $metadata['MpesaReceiptNumber']; $phoneNumber = $metadata['PhoneNumber']; // 此处写你的业务逻辑:比如更新订单状态、发送支付成功通知等 // 给M-PESA返回接收成功的响应,避免重复回调 echo json_encode([ 'ResultCode' => 0, 'ResultDesc' => 'Callback received successfully' ]); exit; } elseif ($resultCode == 1032) { // 用户取消支付逻辑 // 此处写对应的业务逻辑:比如标记订单取消、回滚库存等 echo json_encode([ 'ResultCode' => 0, 'ResultDesc' => 'Cancellation recorded successfully' ]); exit; } else { // 其他错误情况处理 echo json_encode([ 'ResultCode' => 0, 'ResultDesc' => 'Status recorded successfully' ]); exit; } } // 原来的用户表单提交发起支付的逻辑保持不变 if (isset($_POST['submit'])) { $Passkey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; $Amount= $_POST['amt']; $BusinessShortCode = '174379'; $PartyA =$_POST['phone']; $AccountReference =$_POST['name']; $TransactionDesc = 'test'; $Timestamp =date('YmdHis'); $Password = base64_encode($BusinessShortCode.$Passkey.$Timestamp); $headers=['Content-Type:application/json; charset=utf8']; $initiate_url='https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest'; $callBackURL ='https://xxxxxxxxxxxxxxxxx.ngrok.io/processL/transact.php'; function accessToken() { $ConsumerKey = 'ubYsxxxxxxxxxxxxx'; $ConsumerSecret = 'xxxxxxxxxxxxx'; $credentials = base64_encode($ConsumerKey.":".$ConsumerSecret); $url = "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials"; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_HTTPHEADER, array("Authorization: Basic ".$credentials,"Content-Type:application/json")); curl_setopt($curl, CURLOPT_HEADER, false); // curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $curl_response = curl_exec($curl); $access_token=json_decode($curl_response); curl_close($curl); return $access_token->access_token; } $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $initiate_url); curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type:application/json','Authorization:Bearer '.accessToken())); $curl_post_data = array( 'BusinessShortCode' =>$BusinessShortCode, 'Password' => $Password, 'Timestamp' => $Timestamp, 'TransactionType' => 'CustomerPayBillOnline', 'Amount' => $Amount, 'PartyA' => $PartyA, 'PartyB' => $BusinessShortCode, 'PhoneNumber' => $PartyA, 'CallBackURL' => $callBackURL, 'AccountReference' => $AccountReference, 'TransactionDesc' => $TransactionDesc ); $data_string = json_encode($curl_post_data); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string); $curl_response = curl_exec($curl); print_r($curl_response."<br>"); } ?>
3. 注意事项
- 回调处理逻辑必须返回符合要求的JSON响应,否则M-PESA会认为回调失败,按照规则重复推送多次回调请求。
- 生产环境建议增加参数校验,比如验证MerchantRequestID和CheckoutRequestID是否和你本地存储的请求记录一致,避免伪造回调请求。
内容的提问来源于stack exchange,提问作者The_Ogre
相关产品推荐
相关产品推荐

