Skip to content

Commit 1dbca8f

Browse files
committed
feat: initial version of MpesaClient (facade)
1 parent edcb5ac commit 1dbca8f

18 files changed

Lines changed: 690 additions & 4 deletions

mpesa_sdk/bill_manager/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
BillManagerPaymentNotificationResponse,
1515
BillManagerPaymentAcknowledgmentRequest,
1616
BillManagerPaymentAcknowledgmentResponse,
17+
InvoiceItem,
1718
)
1819

1920
from .bill_manager import BillManager
@@ -35,4 +36,5 @@
3536
"BillManagerPaymentAcknowledgmentRequest",
3637
"BillManagerPaymentAcknowledgmentResponse",
3738
"BillManager",
39+
"InvoiceItem",
3840
]

mpesa_sdk/client.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""MpesaClient: A unified client for M-PESA services."""
2+
3+
from mpesa_sdk.auth import TokenManager
4+
from mpesa_sdk.http_client import HttpClient
5+
from mpesa_sdk.services import (
6+
B2BService,
7+
B2CService,
8+
BalanceService,
9+
BillService,
10+
C2BService,
11+
DynamicQRCodeService,
12+
StkPushService,
13+
RatibaService,
14+
ReversalService,
15+
TaxService,
16+
TransactionService,
17+
)
18+
19+
20+
class MpesaClient:
21+
"""Unified client for all M-PESA services."""
22+
23+
def __init__(self, http_client: HttpClient, token_manager: TokenManager):
24+
"""Initialize the MpesaClient with all service facades."""
25+
self.config = {
26+
"http_client": http_client,
27+
"token_manager": token_manager,
28+
}
29+
self.express = StkPushService(**self.config)
30+
self.b2c = B2CService(**self.config)
31+
self.b2b = B2BService(**self.config)
32+
self.transactions = TransactionService(**self.config)
33+
self.tax = TaxService(**self.config)
34+
self.balance = BalanceService(**self.config)
35+
self.reversal = ReversalService(**self.config)
36+
self.bill = BillService(**self.config)
37+
self.dynamic_qr = DynamicQRCodeService(**self.config)
38+
self.c2b = C2BService(**self.config)
39+
self.ratiba = RatibaService(**self.config)

mpesa_sdk/services/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from .b2b import B2BService
2+
from .b2c import B2CService
3+
from .balance import BalanceService
4+
from .bill import BillService
5+
from .c2b import C2BService
6+
from .dynamic_qr import DynamicQRCodeService
7+
from .express import StkPushService
8+
from .ratiba import RatibaService
9+
from .reversal import ReversalService
10+
from .tax import TaxService
11+
from .transaction import TransactionService
12+
13+
__all__ = [
14+
"B2BService",
15+
"B2CService",
16+
"BalanceService",
17+
"BillService",
18+
"C2BService",
19+
"DynamicQRCodeService",
20+
"StkPushService",
21+
"RatibaService",
22+
"ReversalService",
23+
"TaxService",
24+
"TransactionService",
25+
]

mpesa_sdk/services/account_balance.py

Whitespace-only changes.

mpesa_sdk/services/auth.py

Whitespace-only changes.

mpesa_sdk/services/b2b.py

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
"""Facade for M-Pesa B2B APIs (Express Checkout)."""
22

3+
from typing import Optional
34
from mpesa_sdk.auth import TokenManager
45
from mpesa_sdk.http_client import HttpClient
6+
from mpesa_sdk.business_buy_goods import (
7+
BusinessBuyGoods,
8+
BusinessBuyGoodsRequest,
9+
)
10+
from mpesa_sdk.business_paybill import (
11+
BusinessPayBill,
12+
BusinessPayBillRequest,
13+
BusinessPayBillResponse,
14+
)
515
from mpesa_sdk.B2B_express_checkout import (
616
B2BExpressCheckout,
717
B2BExpressCheckoutRequest,
@@ -19,6 +29,14 @@ def __init__(self, http_client: HttpClient, token_manager: TokenManager) -> None
1929
self._express_checkout = B2BExpressCheckout(
2030
http_client=self.http_client, token_manager=self.token_manager
2131
)
32+
self._business_paybill = BusinessPayBill(
33+
http_client=self.http_client,
34+
token_manager=self.token_manager,
35+
)
36+
self._business_buygoods = BusinessBuyGoods(
37+
http_client=self.http_client,
38+
token_manager=self.token_manager,
39+
)
2240

2341
def express_checkout(
2442
self,
@@ -31,7 +49,7 @@ def express_checkout(
3149
request_ref_id: str,
3250
**kwargs,
3351
) -> B2BExpressCheckoutResponse:
34-
"""Initiate a B2B Express Checkout USSD Push transaction.
52+
"""Initiate a B2B Express Checkout USSD Push transaction to another merchant.
3553
3654
Args:
3755
primary_short_code: The primary short code for the transaction.
@@ -61,3 +79,109 @@ def express_checkout(
6179
},
6280
)
6381
return self._express_checkout.ussd_push(request)
82+
83+
def paybill(
84+
self,
85+
initiator: str,
86+
security_credential: str,
87+
amount: int,
88+
party_a: int,
89+
party_b: int,
90+
account_reference: str,
91+
requester: str,
92+
remarks: str,
93+
queue_timeout_url: str,
94+
result_url: str,
95+
**kwargs,
96+
) -> BusinessPayBillResponse:
97+
"""Initiate a Business PayBill transaction to another merchant.
98+
99+
Args:
100+
initiator: API username.
101+
security_credential: Encrypted credential.
102+
amount: The amount to be transacted.
103+
party_a: The sender short code.
104+
party_b: The receiver short code.
105+
account_reference: Reference for the account.
106+
requester: Requester phone number.
107+
remarks: Remarks for the transaction.
108+
queue_timeout_url: URL for timeout callback.
109+
result_url: URL for result callback.
110+
kwargs: Additional fields for BusinessPayBillRequest.
111+
112+
Returns:
113+
BusinessPayBillResponse: Response from M-Pesa API.
114+
"""
115+
request = BusinessPayBillRequest(
116+
Initiator=initiator,
117+
SecurityCredential=security_credential,
118+
Amount=amount,
119+
PartyA=party_a,
120+
PartyB=party_b,
121+
AccountReference=account_reference,
122+
Requester=requester,
123+
Remarks=remarks,
124+
QueueTimeOutURL=queue_timeout_url,
125+
ResultURL=result_url,
126+
**{
127+
k: v
128+
for k, v in kwargs.items()
129+
if k in BusinessPayBillRequest.model_fields
130+
},
131+
)
132+
133+
return self._business_paybill.paybill(request)
134+
135+
def buygoods(
136+
self,
137+
initiator: str,
138+
security_credential: str,
139+
amount: int,
140+
party_a: int,
141+
party_b: int,
142+
account_reference: str,
143+
requester: str,
144+
remarks: str,
145+
queue_timeout_url: str,
146+
result_url: str,
147+
occassion: Optional[str] = None,
148+
**kwargs,
149+
):
150+
"""Initiate a Business Buy Goods transaction to another merchant.
151+
152+
Args:
153+
initiator: API username.
154+
security_credential: Encrypted credential.
155+
amount: The amount to be transacted.
156+
party_a: The sender short code.
157+
party_b: The receiver short code.
158+
account_reference: Reference for the account.
159+
requester: Requester phone number.
160+
remarks: Remarks for the transaction.
161+
queue_timeout_url: URL for timeout callback.
162+
result_url: URL for result callback.
163+
occassion: Optional transaction occasion.
164+
kwargs: Additional fields for BusinessBuyGoodsRequest.
165+
166+
Returns:
167+
BusinessBuyGoodsResponse: Response from M-Pesa API.
168+
"""
169+
request = BusinessBuyGoodsRequest(
170+
Initiator=initiator,
171+
SecurityCredential=security_credential,
172+
Amount=amount,
173+
PartyA=party_a,
174+
PartyB=party_b,
175+
AccountReference=account_reference,
176+
Requester=requester,
177+
Remarks=remarks,
178+
QueueTimeOutURL=queue_timeout_url,
179+
ResultURL=result_url,
180+
Occassion=occassion,
181+
**{
182+
k: v
183+
for k, v in kwargs.items()
184+
if k in BusinessBuyGoodsRequest.model_fields
185+
},
186+
)
187+
return self._business_buygoods.buy_goods(request)

mpesa_sdk/services/balance.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""Facade for M-Pesa Account Balance API interactions."""
2+
3+
from typing import Optional
4+
from mpesa_sdk.auth import TokenManager
5+
from mpesa_sdk.http_client import HttpClient
6+
from mpesa_sdk.account_balance import (
7+
AccountBalance,
8+
AccountBalanceRequest,
9+
AccountBalanceResponse,
10+
)
11+
12+
13+
class BalanceService:
14+
"""Facade for M-Pesa Account Balance operations."""
15+
16+
def __init__(self, http_client: HttpClient, token_manager: TokenManager) -> None:
17+
"""Initialize the Balance service."""
18+
self.http_client = http_client
19+
self.token_manager = token_manager
20+
self.account_balance = AccountBalance(
21+
http_client=self.http_client,
22+
token_manager=self.token_manager,
23+
)
24+
25+
def query(
26+
self,
27+
initiator: str,
28+
security_credential: str,
29+
command_id: str,
30+
party_a: int,
31+
identifier_type: int,
32+
remarks: str,
33+
result_url: str,
34+
queue_timeout_url: str,
35+
**kwargs,
36+
) -> AccountBalanceResponse:
37+
"""Query account balance.
38+
39+
Args:
40+
initiator: Name of the initiator.
41+
security_credential: Security credential for authentication.
42+
command_id: Command ID for the transaction.
43+
party_a: Shortcode of the account to query.
44+
identifier_type: Type of identifier for PartyA.
45+
remarks: Additional remarks.
46+
result_url: URL for result notification.
47+
queue_timeout_url: URL for timeout notification.
48+
**kwargs: Additional fields for AccountBalanceRequest.
49+
50+
Returns:
51+
AccountBalanceResponse: Response from the M-Pesa API.
52+
"""
53+
request = AccountBalanceRequest(
54+
Initiator=initiator,
55+
SecurityCredential=security_credential,
56+
CommandID=command_id,
57+
PartyA=party_a,
58+
IdentifierType=identifier_type,
59+
Remarks=remarks,
60+
ResultURL=result_url,
61+
QueueTimeOutURL=queue_timeout_url,
62+
**{
63+
k: v
64+
for k, v in kwargs.items()
65+
if k in AccountBalanceRequest.model_fields
66+
},
67+
)
68+
return self.account_balance.query(request)

0 commit comments

Comments
 (0)