Skip to content

Commit 0b8fa21

Browse files
authored
Merge pull request #35 from RafaelJohn9/feat/services
Feat/services
2 parents 926ca16 + c3aab28 commit 0b8fa21

35 files changed

Lines changed: 2246 additions & 41 deletions

mpesa_sdk/B2C/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
B2CResultParameter,
66
B2CResultMetadata,
77
B2CResultCallback,
8-
B2CTimeoutCallbackResponse,
98
B2CTimeoutCallback,
109
B2CTimeoutCallbackResponse,
1110
)

mpesa_sdk/C2B/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
C2BValidationResponse,
66
C2BConfirmationResponse,
77
C2BValidationResultCodeType,
8+
C2BResponseType,
89
)
910
from .C2B import C2B
1011

1112
__all__ = [
13+
"C2BResponseType",
1214
"C2BRegisterUrlRequest",
1315
"C2BRegisterUrlResponse",
1416
"C2BValidationRequest",

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/bill_manager/schemas.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ class BillManagerOptInResponse(BaseModel):
5656
}
5757
)
5858

59+
def is_successful(self) -> bool:
60+
"""Checks if the response indicates success."""
61+
return self.rescode == "200"
62+
5963

6064
# Update Opt-in Details API
6165
class BillManagerUpdateOptInRequest(BaseModel):
@@ -98,6 +102,10 @@ class BillManagerUpdateOptInResponse(BaseModel):
98102
json_schema_extra={"example": {"resmsg": "Success", "rescode": "200"}}
99103
)
100104

105+
def is_successful(self) -> bool:
106+
"""Checks if the response indicates success."""
107+
return self.rescode == "200"
108+
101109

102110
# Invoice Item
103111
class InvoiceItem(BaseModel):
@@ -324,6 +332,10 @@ class BillManagerBulkInvoiceResponse(BaseModel):
324332
}
325333
)
326334

335+
def is_successful(self) -> bool:
336+
"""Checks if the response indicates success."""
337+
return self.rescode == "200"
338+
327339

328340
# Cancel Single Invoice API
329341
class BillManagerCancelSingleInvoiceRequest(BaseModel):

mpesa_sdk/dynamic_qr_code/schemas.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"""
66

77
from enum import Enum
8-
from typing import Optional
98
from pydantic import BaseModel, Field, ConfigDict
109
from pydantic import model_validator
1110
from mpesa_sdk.utils.phone import normalize_phone_number

mpesa_sdk/mpesa_client.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""MpesaClient: A unified client for M-PESA services."""
2+
3+
from mpesa_sdk.auth import TokenManager
4+
from mpesa_sdk.http_client import MpesaHttpClient
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, consumer_key: str, consumer_secret: str) -> None:
24+
"""Initialize the MpesaClient with all service facades."""
25+
self.http_client = MpesaHttpClient()
26+
self.token_manager = TokenManager(
27+
http_client=self.http_client,
28+
consumer_key=consumer_key,
29+
consumer_secret=consumer_secret,
30+
)
31+
32+
# express => M-PESA STK Push
33+
self.express = StkPushService(
34+
http_client=self.http_client, token_manager=self.token_manager
35+
)
36+
37+
# b2c => M-PESA Business to Customer services
38+
self.b2c = B2CService(
39+
http_client=self.http_client, token_manager=self.token_manager
40+
)
41+
42+
# b2b => M-PESA Business to Business services
43+
self.b2b = B2BService(
44+
http_client=self.http_client, token_manager=self.token_manager
45+
)
46+
47+
# transaction => M-PESA Transaction status services
48+
self.transactions = TransactionService(
49+
http_client=self.http_client, token_manager=self.token_manager
50+
)
51+
52+
# tax => M-PESA Tax services
53+
self.tax = TaxService(
54+
http_client=self.http_client, token_manager=self.token_manager
55+
)
56+
57+
# balance => M-PESA Account balance services
58+
self.balance = BalanceService(
59+
http_client=self.http_client, token_manager=self.token_manager
60+
)
61+
62+
# reversal => M-PESA Transaction reversal services
63+
self.reversal = ReversalService(
64+
http_client=self.http_client, token_manager=self.token_manager
65+
)
66+
67+
# bill => M-PESA Bill services
68+
self.bill = BillService(
69+
http_client=self.http_client, token_manager=self.token_manager
70+
)
71+
72+
# dynamic_qr => M-PESA Dynamic QR services
73+
self.dynamic_qr = DynamicQRCodeService(
74+
http_client=self.http_client, token_manager=self.token_manager
75+
)
76+
77+
# c2b => M-PESA Customer to Business services
78+
self.c2b = C2BService(
79+
http_client=self.http_client, token_manager=self.token_manager
80+
)
81+
82+
# ratiba => M-PESA Ratiba services
83+
self.ratiba = RatibaService(
84+
http_client=self.http_client, token_manager=self.token_manager
85+
)

mpesa_sdk/reversal/schemas.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
It includes models for reversal requests, responses, and result notifications.
44
"""
55

6-
from enum import Enum
76
from pydantic import BaseModel, Field, ConfigDict, model_validator
87
from typing import Optional, List
98

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/b2b.py

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
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+
BusinessBuyGoodsResponse,
10+
)
11+
from mpesa_sdk.business_paybill import (
12+
BusinessPayBill,
13+
BusinessPayBillRequest,
14+
BusinessPayBillResponse,
15+
)
516
from mpesa_sdk.B2B_express_checkout import (
617
B2BExpressCheckout,
718
B2BExpressCheckoutRequest,
@@ -19,6 +30,14 @@ def __init__(self, http_client: HttpClient, token_manager: TokenManager) -> None
1930
self._express_checkout = B2BExpressCheckout(
2031
http_client=self.http_client, token_manager=self.token_manager
2132
)
33+
self._business_paybill = BusinessPayBill(
34+
http_client=self.http_client,
35+
token_manager=self.token_manager,
36+
)
37+
self._business_buygoods = BusinessBuyGoods(
38+
http_client=self.http_client,
39+
token_manager=self.token_manager,
40+
)
2241

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

0 commit comments

Comments
 (0)