You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取并处理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 04:48:02