Skip to content

Commit 3d681d7

Browse files
authored
Merge pull request #33 from RafaelJohn9/dev
Dev
2 parents 0367bc2 + 6f4ba5f commit 3d681d7

File tree

66 files changed

+7601
-86
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+7601
-86
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""B2BExpressCheckout: Handles M-Pesa B2B Express Checkout API interactions.
2+
3+
This module provides functionality to initiate a B2B Express Checkout USSD Push transaction and handle result/timeout notifications
4+
using the M-Pesa API. Requires a valid access token for authentication and uses the HttpClient for HTTP requests.
5+
"""
6+
7+
from pydantic import BaseModel, ConfigDict
8+
from mpesa_sdk.auth import TokenManager
9+
from mpesa_sdk.http_client import HttpClient
10+
11+
from .schemas import (
12+
B2BExpressCheckoutRequest,
13+
B2BExpressCheckoutResponse,
14+
)
15+
16+
17+
class B2BExpressCheckout(BaseModel):
18+
"""Represents the B2B Express Checkout API client for M-Pesa operations.
19+
20+
https://developer.safaricom.co.ke/APIs/B2BExpressCheckout
21+
22+
Attributes:
23+
http_client (HttpClient): HTTP client for making requests to the M-Pesa API.
24+
token_manager (TokenManager): Manages access tokens for authentication.
25+
"""
26+
27+
http_client: HttpClient
28+
token_manager: TokenManager
29+
30+
model_config = ConfigDict(arbitrary_types_allowed=True)
31+
32+
def ussd_push(
33+
self, request: B2BExpressCheckoutRequest
34+
) -> B2BExpressCheckoutResponse:
35+
"""Initiates a B2B Express Checkout USSD Push transaction.
36+
37+
Args:
38+
request (B2BExpressCheckoutRequest): The B2B Express Checkout request data.
39+
40+
Returns:
41+
B2BExpressCheckoutResponse: Response from the M-Pesa API.
42+
"""
43+
url = "/v1/ussdpush/get-msisdn"
44+
headers = {
45+
"Authorization": f"Bearer {self.token_manager.get_token()}",
46+
"Content-Type": "application/json",
47+
}
48+
response_data = self.http_client.post(
49+
url, json=request.model_dump(mode="json"), headers=headers
50+
)
51+
return B2BExpressCheckoutResponse(**response_data)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from .schemas import (
2+
B2BExpressCheckoutRequest,
3+
B2BExpressCheckoutResponse,
4+
B2BExpressCheckoutCallback,
5+
B2BExpressCallbackResponse,
6+
)
7+
8+
from .B2B_express_checkout import B2BExpressCheckout
9+
10+
__all__ = [
11+
"B2BExpressCheckout",
12+
"B2BExpressCheckoutRequest",
13+
"B2BExpressCheckoutResponse",
14+
"B2BExpressCheckoutCallback",
15+
"B2BExpressCallbackResponse",
16+
]
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""Schemas for M-PESA B2B Express Checkout APIs."""
2+
3+
from pydantic import BaseModel, Field, ConfigDict, HttpUrl
4+
from typing import Optional
5+
6+
7+
class B2BExpressCheckoutRequest(BaseModel):
8+
"""Request schema for B2B Express Checkout USSD Push."""
9+
10+
primaryShortCode: int = Field(
11+
..., description="Merchant's till (debit party) shortcode/tillNumber."
12+
)
13+
receiverShortCode: int = Field(
14+
..., description="Vendor's paybill (credit party) shortcode."
15+
)
16+
amount: int = Field(..., description="Amount to be sent to vendor.")
17+
paymentRef: str = Field(
18+
..., description="Reference for the payment (appears in text for merchant)."
19+
)
20+
callbackUrl: HttpUrl = Field(
21+
..., description="Vendor system endpoint for confirmation response."
22+
)
23+
partnerName: str = Field(..., description="Vendor's organization friendly name.")
24+
RequestRefID: str = Field(..., description="Unique identifier for each request.")
25+
26+
model_config = ConfigDict(
27+
json_schema_extra={
28+
"example": {
29+
"primaryShortCode": 123456,
30+
"receiverShortCode": 654321,
31+
"amount": 100,
32+
"paymentRef": "Invoice123",
33+
"callbackUrl": "http://example.com/result",
34+
"partnerName": "VendorName",
35+
"RequestRefID": "550e8400-e29b-41d4-a716-446655440000",
36+
}
37+
}
38+
)
39+
40+
41+
class B2BExpressCheckoutResponse(BaseModel):
42+
"""Acknowledgment response schema for B2B Express Checkout USSD Push."""
43+
44+
code: str = Field(
45+
..., description="Shows if the push was successful (0) or failed."
46+
)
47+
status: str = Field(..., description="USSD initiation status message.")
48+
49+
model_config = ConfigDict(
50+
json_schema_extra={
51+
"example": {
52+
"code": "0",
53+
"status": "USSD Initiated Successfully",
54+
}
55+
}
56+
)
57+
58+
def is_successful(self) -> bool:
59+
"""Check if the response indicates a successful USSD initiation."""
60+
return self.code == "0"
61+
62+
63+
class B2BExpressCheckoutCallback(BaseModel):
64+
"""Callback response schema for B2B Express Checkout USSD Push."""
65+
66+
resultCode: str = Field(
67+
..., description="Status code: 0=success, other=fail/cancelled."
68+
)
69+
resultDesc: str = Field(..., description="Description of transaction result.")
70+
amount: Optional[float] = Field(None, description="Amount initiated for payment.")
71+
requestId: str = Field(..., description="Unique identifier of the request.")
72+
paymentReference: Optional[str] = Field(
73+
None, description="Reference for the payment."
74+
)
75+
resultType: Optional[str] = Field(
76+
None, description="Status code for transaction sent to listener."
77+
)
78+
conversationID: Optional[str] = Field(
79+
None, description="Global unique transaction request ID from M-Pesa."
80+
)
81+
transactionId: Optional[str] = Field(
82+
None, description="Mpesa Receipt No of the transaction."
83+
)
84+
status: Optional[str] = Field(
85+
None, description="Transaction status (SUCCESS/FAILED)."
86+
)
87+
88+
model_config = ConfigDict(
89+
json_schema_extra={
90+
"example": {
91+
"resultCode": "0",
92+
"resultDesc": "The service request is processed successfully.",
93+
"amount": "71.0",
94+
"requestId": "404e1aec-19e0-4ce3-973d-bd92e94c8021",
95+
"resultType": "0",
96+
"conversationID": "AG_20230426_2010434680d9f5a73766",
97+
"transactionId": "RDQ01NFT1Q",
98+
"status": "SUCCESS",
99+
}
100+
}
101+
)
102+
103+
def is_successful(self) -> bool:
104+
"""Check if the callback indicates a successful transaction."""
105+
return self.resultCode == "0"
106+
107+
108+
class B2BExpressCallbackResponse(BaseModel):
109+
"""Response schema for B2B Express Checkout callback."""
110+
111+
ResultCode: int = Field(
112+
default=0,
113+
description="Result code (0=Success, other=Failure).",
114+
)
115+
ResultDesc: str = Field(
116+
default="Callback received successfully.",
117+
description="Result description.",
118+
)
119+
120+
model_config = ConfigDict(
121+
json_schema_extra={
122+
"example": {
123+
"ResultCode": 0,
124+
"ResultDesc": "Callback received successfully.",
125+
}
126+
}
127+
)

mpesa_sdk/B2C/B2C.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""B2C: Handles M-Pesa B2C (Business to Customer) API interactions.
2+
3+
This module provides functionality to initiate B2C payments using the M-Pesa API.
4+
Requires a valid access token for authentication and uses the HttpClient for HTTP requests.
5+
"""
6+
7+
from pydantic import BaseModel, ConfigDict
8+
from mpesa_sdk.auth import TokenManager
9+
from mpesa_sdk.http_client import HttpClient
10+
11+
from .schemas import (
12+
B2CRequest,
13+
B2CResponse,
14+
)
15+
16+
17+
class B2C(BaseModel):
18+
"""Represents the B2C API client for M-Pesa Business to Customer operations.
19+
20+
https://developer.safaricom.co.ke/APIs/BusinessToCustomerPayment
21+
22+
Attributes:
23+
http_client (HttpClient): HTTP client for making requests to the M-Pesa API.
24+
token_manager (TokenManager): Manages access tokens for authentication.
25+
"""
26+
27+
http_client: HttpClient
28+
token_manager: TokenManager
29+
30+
model_config = ConfigDict(arbitrary_types_allowed=True)
31+
32+
def send_payment(self, request: B2CRequest) -> B2CResponse:
33+
"""Initiates a B2C payment request.
34+
35+
Args:
36+
request (B2CRequest): The payment request details.
37+
38+
Returns:
39+
B2CResponse: Response from the M-Pesa API after payment initiation.
40+
"""
41+
url = "/mpesa/b2c/v3/paymentrequest"
42+
headers = {
43+
"Authorization": f"Bearer {self.token_manager.get_token()}",
44+
"Content-Type": "application/json",
45+
}
46+
response_data = self.http_client.post(url, json=dict(request), headers=headers)
47+
return B2CResponse(**response_data)

mpesa_sdk/B2C/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from .schemas import (
2+
B2CCommandIDType,
3+
B2CRequest,
4+
B2CResponse,
5+
B2CResultParameter,
6+
B2CResultMetadata,
7+
B2CResultCallback,
8+
B2CTimeoutCallbackResponse,
9+
B2CTimeoutCallback,
10+
B2CTimeoutCallbackResponse,
11+
)
12+
from .B2C import B2C
13+
14+
__all__ = [
15+
"B2C",
16+
"B2CCommandIDType",
17+
"B2CRequest",
18+
"B2CResponse",
19+
"B2CResultParameter",
20+
"B2CResultMetadata",
21+
"B2CResultCallback",
22+
"B2CTimeoutCallbackResponse",
23+
"B2CTimeoutCallback",
24+
]

0 commit comments

Comments
 (0)