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

集成M-Pesa Daraja API时遭遇Bad Request - Invalid PhoneNumber错误

排查M-Pesa集成中「Bad Request - Invalid PhoneNumber」错误

我正在使用Daraja门户将M-Pesa集成到应用中,但遇到了「Bad Request - Invalid PhoneNumber」错误。尽管使用的是有效的肯尼亚手机号,仍无法成功推进流程。相关代码及错误信息如下:

支付类代码

from api.services.payment.m_pesa_payment.mpes_credential import MpesaC2bCredential
import requests
import logging
import json
class MakePayment:
    @staticmethod
    def make_payment(payload):
        mepesa_acess_token = MpesaAccessToken()
        credential = MpesaC2bCredential()
        access_token = mepesa_acess_token.get_access_token()
        amount = payload["amount"]
        phone_number = payload["phone_number"]
        api_url = "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest"
        headers = {"Authorization": "Bearer %s" % access_token}
        
        request = {
        "BusinessShortCode": credential.short_code,
        "Password": mepesa_acess_token.generate_password(),
        "Timestamp": mepesa_acess_token.current_time,
        "TransactionType": "CustomerPayBillOnline",
        "Amount": amount,
        "PartyA": phone_number,
        "PartyB": credential.short_code,
        "PhoneNumber": phone_number,
        "CallBackURL": "https://ba3f-41-79-219-185.ngrok-free.app/payment",
        "AccountReference": "unknown",
        "TransactionDesc": "Testing stk push"
    }
        
        response = requests.post(api_url, json=request, headers=headers)
        
        response_data = response.json()
        logging.info("Mpesa request data {}".format(response_data))
        logging.info("Mpesa response info {}".format(response_data))

        return response_data

payload = {"phone_number":"+254708374149", "amount": 5000}   
print(MakePayment.make_payment(payload))

凭证类代码

class MpesaC2bCredential:
    mpesa_url = 'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials'
    passe_key = 'xxxxxx'
    
    def __init__(self, consumer_key='xxxx', consumer_secret='xxx', api_urls = mpesa_url, short_code='174379',passe_key = passe_key):
        self.consumer_key = consumer_key
        self.consumer_secret = consumer_secret
        self.api_url = api_urls
        self.short_code = short_code
        self.api_url = api_urls
        self.passe_key = passe_key
        
        @property
        def consumer_key(self):
            return self.__consumer_key
        
        @consumer_key.setter
        def consumer_key(self, value):
            self.__consumer_key = value
            
        @property
        def consumer_secret(self):
            return self.__consumer_secret
        
        @consumer_secret.setter
        def consumer_secret(self, value):
            self.__consumer_secret = value
            
        @property
        def api_url(self):
            return self.__api_url
        
        @api_url.setter
        def api_url(self, value):
            self.__url = value
            
        @property
        def short_code(self):
            return self.__short_code
        
        @short_code.setter
        def short_code(self, value):
            self.__short_code = value
            
        @property
        def passe_key(self):
            return self.__short_code
        
        @passe_key.setter
        def short_code(self, value):
            self.__short_code = value

访问令牌类代码

from api.services.payment.m_pesa_payment.mpes_credential import MpesaC2bCredential
import requests
import json
from requests.auth import HTTPBasicAuth
from datetime import datetime
import base64
import logging
import time

class MpesaAccessToken:
    access_token_expiration = None
    current_time = datetime.now().strftime('%Y%m%d%H%M%S')
    
    def __init__(self, access_token = None, credential = MpesaC2bCredential(), current_time = current_time):
        self.access_token = access_token
        self.credential = credential
        self.current_time = current_time

        
    def get_access_token(self):
        try:
            res = requests.get(
                self.credential.api_url,
                auth=HTTPBasicAuth(self.credential.consumer_key, self.credential.consumer_secret),
            )
        except Exception as err:
            logging.error("Error {}".format(err))
            raise err
        else:
            token = res.json()["access_token"]
            self.headers = {"Authorization": "Bearer %s" % token}
            return token
        
    
    def generate_password(self):
        """Generates mpesa api password using the provided shortcode and passkey"""
        current_date = datetime.now()
        #timestamp = current_date.strftime("%Y%m%d%H%M%S")
        password_str = self.credential.short_code + self.credential.passe_key + self.current_time
        password_bytes = password_str.encode("ascii")
        return base64.b64encode(password_bytes).decode("utf-8")

错误信息

{'requestId': '11231-10026868-1', 'errorCode': '400.002.02', 'errorMessage': 'Bad Request - Invalid PhoneNumber'}

排查原因及解决方法

1. 手机号格式不符合API要求

Daraja API明确要求手机号为不带+号的纯数字格式,前缀必须是254。你的测试手机号+254708374149需要改为254708374149,带+号的格式会直接触发该错误。可以在代码中统一处理格式:

# 在make_payment方法中处理手机号
phone_number = payload["phone_number"].strip().replace("+", "")

2. 凭证类属性定义存在致命错误

MpesaC2bCredential类中passe_key的getter和setter完全错误:

  • @property定义的passe_key返回的是self.__short_code,而非正确的self.__passe_key
  • @passe_key.setter的函数名写成了short_code,导致无法正确设置passe_key属性
    这会导致passe_key值始终等于short_code,虽不直接触发手机号错误,但会引发后续密码生成失败,必须修复:
# 修正后的passe_key属性
@property
def passe_key(self):
    return self.__passe_key

@passe_key.setter
def passe_key(self, value):
    self.__passe_key = value

同时初始化方法中重复赋值self.api_url = api_urls,可删除其中一行,避免冗余。

3. 沙箱环境测试号限制

沙箱环境仅支持Daraja官方提供的测试手机号,真实手机号无法在沙箱中使用。确认你使用的254708374149是沙箱允许的测试号,若使用真实号码会触发错误。

4. 参数一致性检查

确保请求中PartyA和PhoneNumber的手机号格式完全一致,且都符合纯数字要求,避免因参数不一致引发错误。

内容的提问来源于stack exchange,提问作者rriol

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 03:55:00