Example #1
0
def test_valid_saf_numbers() -> None:
    """Test valid safaricom numbers."""
    def subscriber():
        return random.randint(100000, 999999)

    gen700_708 = [f"070{i}{subscriber()}" for i in range(0, 8)]
    gen710_729 = [f"07{i}{subscriber()}" for i in range(10, 29)]
    gen757_759 = [f"07{i}{subscriber()}" for i in range(57, 59)]
    gen790_792 = [f"07{i}{subscriber()}" for i in range(90, 92)]
    safaricom_numbers = gen700_708 + gen710_729 + gen757_759 + gen790_792
    for number in safaricom_numbers:
        _, valid = utils.saf_number_fmt(number)
        assert valid is True
Example #2
0
    async def c2b(
        self,
        shortcode: str = None,
        amount: int = None,
        phone_number: str = None,
    ) -> dict:
        """Simulate making payments from client to Safaricom API.

        Args:
            shortcode: a paybill number/till number, which you expect to receive
                payments notifications about.
            amount: the amount in KSh you are sending to a businees.
            phone_number: a valid safaricom number you are sending money from.

        Returns:
            A dict with the ResponseDescription having a value as shown.

            {
                "ConversationID": "AG_20180324_000066530b914eee3f85",
                "OriginatorCoversationID": "25344-885903-1",
                "ResponseDescription": "Accept the service request successfully."
            }

            Any other value is a failed request.

        Raises:
            ValueError: when the phone_number supplied is not a valid phone number.
        """
        url = f"{self.base_url}{self.C2B_URL_PATH}"
        number, valid = saf_number_fmt(phone_number)
        if not valid:
            raise ValueError(f"{phone_number} is not a valid Safaricom number")

        data = {
            "ShortCode": shortcode,
            "CommandID": "CustomerPayBillOnline",
            "Amount": f"{amount}",
            "Msisdn": number,
            "BillRefNumber": number,
        }
        headers = await self._get_headers()

        async with aiohttp.ClientSession(headers=headers) as session:
            return await self.post(session=session, url=url, data=data)
Example #3
0
    async def stk_push(
        self,
        lipa_na_mpesa_shortcode: str = None,
        lipa_na_mpesa_passkey: str = None,
        amount: int = None,
        party_a: str = None,
        party_b: str = None,
        callback_url: str = None,
        transaction_desc: str = None,
        transaction_type: str = "CustomerPayBillOnline",
    ) -> dict:
        """Make an STK push.

        Args:
            lipa_na_mpesa_shortcode: shortcode of the organization initiating the
                request and expecting the payment.
            lipa_na_mpesa_passkey: lipa na mpesa pass key.
            amount: amount being sent from client to business.
            party_a: debit party of the transaction, hereby the phone number of
                the customer.
            party_b: credit party of the transaction, hereby being the shortcode
                of the organization.
            callback_url: the endpoint where you want the results of the transaction
                delivered.
            transaction_desc: a short description of the transaction.
            transaction_type: specifies the type of transaction being performed.

        Raises:
            ValueError: when the party_b value is not a valid Safaricom number.

        Returns:
            a dict with the ResponseCode value as 0.

            {
                "MerchantRequestID": "25353-1377561-4",
                "CheckoutRequestID": "ws_CO_26032018185226297",
                "ResponseCode": "0",
                "ResponseDescription": "Success. Request accepted for processing",
                "CustomerMessage": "Success. Request accepted for processing"
            }

            Any other value is a failed request.
        """
        url = f"{self.base_url}{self.STK_URL_PATH}"
        number, valid = saf_number_fmt(party_a)
        if not valid:
            raise ValueError(f"{party_a} is not a valid Safaricom number")

        password, timestamp = Mpesa.generate_password(lipa_na_mpesa_shortcode,
                                                      lipa_na_mpesa_passkey)
        data = {
            "BusinessShortCode": lipa_na_mpesa_shortcode,
            "Password": password,
            "Timestamp": timestamp,
            "TransactionType": transaction_type,
            "Amount": f"{amount}",
            "PartyA": number,
            "PartyB": lipa_na_mpesa_shortcode,
            "PhoneNumber": number,
            "CallBackURL": callback_url,
            "AccountReference": number,
            "TransactionDesc": transaction_desc,
        }
        headers = await self._get_headers()

        async with aiohttp.ClientSession(headers=headers) as session:
            return await self.post(session=session, url=url, data=data)
Example #4
0
    async def b2c(
        self,
        initiator_name: str = None,
        security_credential: str = None,
        command_id: str = None,
        amount: int = None,
        party_a: str = None,
        party_b: str = None,
        remarks: str = None,
        queue_timeout_url: str = None,
        result_url: str = None,
        occassion: str = "",
    ) -> dict:
        """Make a payment from MPESA to a client.

        Args:
            initiator_name: username of the API operator as assigned on the MPesa
                Org Portal.
            security_credential: password of the API operator encrypted using the
                public key certificate provided.
            command_id: specifies the type of transaction being performed. There
                are three allowed values on the API: SalaryPayment, BusinessPayment
                or PromotionPayment.
            amount: the amount being sent from a business to a client.
            party_a: the business shortcode.
            party_b: the client phone number.
            remarks: A very short description of the transaction from your end.
            queue_timeout_url: the callback URL used to send an error callback
                when the transaction was not able to be processed by MPesa within
                a stipulated time period.
            result_url: the callback URL where the results of the transaction will be
                sent.
            occassion: A very short description of the transaction from your end. Can
                be left blank.

        Returns:
            A dict with a ResponseCode value of 0:

            {
                "ConversationID": "AG_20180326_00005ca7f7c21d608166",
                "OriginatorConversationID": "12363-1328499-6",
                "ResponseCode": "0",
                "ResponseDescription": "Accept the service request successfully."
            }

            Any other returned value is a failed request.

        Raises:
            ValueError: when the party_b is not a valid Safaricom number or when
                command_id is not SalaryPayment, BusinessPayment or
                PromotionPayment.
        """
        url = f"{self.base_url}{self.B2C_URL_PATH}"
        phone_number, valid = saf_number_fmt(party_b)
        if not valid:
            raise ValueError(f"{party_b} is not a valid Safaricom number")
        if command_id not in [
                "SalaryPayment",
                "BusinessPayment",
                "PromotionPayment",
        ]:
            raise ValueError(f"{command_id} is not a valid CommandID value")
        data = {
            "InitiatorName": initiator_name,
            "SecurityCredential": security_credential,
            "CommandID": command_id,
            "Amount": f"{amount}",
            "PartyA": party_a,
            "PartyB": phone_number,
            "Remarks": remarks,
            "QueueTimeOutURL": queue_timeout_url,
            "ResultURL": result_url,
            "Occassion": occassion,
        }
        headers = await self._get_headers()

        async with aiohttp.ClientSession(headers=headers) as session:
            return await self.post(session=session, url=url, data=data)
Example #5
0
def test_invalid_saf_numbers() -> None:
    """Test invalid Safaricom numbers."""
    _, valid = utils.saf_number_fmt("0731100100")
    assert valid is False