mpesakit 2.1.1__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mpesakit-2.1.1 → mpesakit-2.2.0}/AUTHORS.md +2 -0
- mpesakit-2.2.0/PKG-INFO +321 -0
- mpesakit-2.2.0/README.md +267 -0
- mpesakit-2.2.0/mpesakit/__init__.py +5 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_client.py +193 -101
- mpesakit-2.2.0/mpesakit/services/__init__.py +36 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/b2c.py +87 -3
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/balance.py +48 -2
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/bill.py +118 -2
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/c2b.py +40 -2
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/dynamic_qr.py +44 -2
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/express.py +78 -2
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/ratiba.py +56 -2
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/reversal.py +46 -2
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/tax.py +48 -2
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/transaction.py +62 -2
- mpesakit-2.1.1/PKG-INFO +0 -217
- mpesakit-2.1.1/README.md +0 -163
- mpesakit-2.1.1/mpesakit/__init__.py +0 -5
- mpesakit-2.1.1/mpesakit/services/__init__.py +0 -25
- {mpesakit-2.1.1 → mpesakit-2.2.0}/.gitignore +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/LICENSE +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/account_balance/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/account_balance/account_balance.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/account_balance/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/auth/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/auth/access_token.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/auth/token_manager.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2b_express_checkout/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2b_express_checkout/b2b_express_checkout.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2b_express_checkout/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2c/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2c/b2c.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2c/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2c_account_top_up/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2c_account_top_up/b2c_account_top_up.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2c_account_top_up/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/bill_manager/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/bill_manager/bill_manager.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/bill_manager/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/business_buy_goods/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/business_buy_goods/business_buy_goods.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/business_buy_goods/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/business_paybill/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/business_paybill/business_paybill.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/business_paybill/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/c2b/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/c2b/c2b.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/c2b/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/dynamic_qr_code/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/dynamic_qr_code/dynamic_qr_code.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/dynamic_qr_code/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/errors.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/http_client/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/http_client/http_client.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/http_client/mpesa_async_http_client.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/http_client/mpesa_http_client.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_express/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_express/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_express/stk_push.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_ratiba/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_ratiba/mpesa_ratiba.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_ratiba/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/reversal/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/reversal/reversal.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/reversal/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/security/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/security/certs/ProductionCertificate.cer +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/security/certs/SandboxCertificate.cer +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/security/get_credential.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/security/ip_whitelist.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/b2b.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/tax_remittance/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/tax_remittance/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/tax_remittance/tax_remittance.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/transaction_status/__init__.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/transaction_status/schemas.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/transaction_status/transaction_status.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/utils/phone.py +0 -0
- {mpesakit-2.1.1 → mpesakit-2.2.0}/pyproject.toml +0 -0
mpesakit-2.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mpesakit
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: A Python SDK for integrating with M-Pesa APIs
|
|
5
|
+
Project-URL: Homepage, https://github.com/Byte-Barn/mpesakit
|
|
6
|
+
Project-URL: Issues, https://github.com/Byte-Barn/mpesakit/issues
|
|
7
|
+
Project-URL: Repository, https://github.com/Byte-Barn/mpesakit
|
|
8
|
+
Author-email: John Kagunda <johnmkagunda@gmail.com>
|
|
9
|
+
Maintainer-email: John Kagunda <johnmkagunda@gmail.com>
|
|
10
|
+
License: APACHE-2.0
|
|
11
|
+
License-File: AUTHORS.md
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: africa,api,django,fastapi,flask,mobile money,mpesa,payment,payments,rest,sdk
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: cryptography>=41.0.7
|
|
26
|
+
Requires-Dist: httpx<1.0.0,>=0.27.0
|
|
27
|
+
Requires-Dist: pydantic<3.0.0,>=2.10.6
|
|
28
|
+
Requires-Dist: pydantic[email]<3.0.0,>=2.10.6
|
|
29
|
+
Requires-Dist: requests<3.0.0,>=2.32.5
|
|
30
|
+
Requires-Dist: tenacity<10.0.0,>=9.1.2
|
|
31
|
+
Requires-Dist: typing-extensions<5.0.0,>=4.12.2
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: bandit; extra == 'dev'
|
|
34
|
+
Requires-Dist: build; extra == 'dev'
|
|
35
|
+
Requires-Dist: coverage; extra == 'dev'
|
|
36
|
+
Requires-Dist: hatch; extra == 'dev'
|
|
37
|
+
Requires-Dist: hatch-vcs; extra == 'dev'
|
|
38
|
+
Requires-Dist: hatchling; extra == 'dev'
|
|
39
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest-asyncio<1.0.0,>=0.23.6; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
43
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
44
|
+
Requires-Dist: types-requests; extra == 'dev'
|
|
45
|
+
Provides-Extra: test
|
|
46
|
+
Requires-Dist: bandit; extra == 'test'
|
|
47
|
+
Requires-Dist: mypy; extra == 'test'
|
|
48
|
+
Requires-Dist: pyngrok; extra == 'test'
|
|
49
|
+
Requires-Dist: pytest; extra == 'test'
|
|
50
|
+
Requires-Dist: pytest-asyncio<1.0.0,>=0.23.6; extra == 'test'
|
|
51
|
+
Requires-Dist: pytest-cov; extra == 'test'
|
|
52
|
+
Requires-Dist: types-requests; extra == 'test'
|
|
53
|
+
Description-Content-Type: text/markdown
|
|
54
|
+
|
|
55
|
+
# mpesakit
|
|
56
|
+
|
|
57
|
+
> ⚡ Effortless M-Pesa integration using Safaricom's Daraja API — built for developers, by developers.
|
|
58
|
+
|
|
59
|
+
[](https://www.python.org/downloads/)
|
|
60
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
61
|
+
[](https://pypi.org/project/mpesakit)
|
|
62
|
+
[](https://pepy.tech/project/mpesakit)
|
|
63
|
+
[](https://github.com/Byte-Barn/mpesakit/actions)
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
Integrating Safaricom's Daraja API from scratch means wrestling with OAuth2 token rotation, security credential encryption, inconsistent sandbox vs. production endpoints, and documentation that rarely connects end-to-end.
|
|
68
|
+
|
|
69
|
+
**`mpesakit`** handles all of that. Add your credentials, call a method, move on.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Installation
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install mpesakit
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Quick Start
|
|
82
|
+
|
|
83
|
+
### 1. Set your credentials
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
export MPESA_CONSUMER_KEY="your_consumer_key"
|
|
87
|
+
export MPESA_CONSUMER_SECRET="your_consumer_secret"
|
|
88
|
+
export MPESA_SHORTCODE="your_shortcode"
|
|
89
|
+
export MPESA_PASSKEY="your_lipa_na_mpesa_passkey"
|
|
90
|
+
export MPESA_PHONE_NUMBER="254712345678"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 2. Trigger an STK Push
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import os
|
|
97
|
+
from dotenv import load_dotenv
|
|
98
|
+
from mpesakit import MpesaClient
|
|
99
|
+
from mpesakit.mpesa_express import TransactionType
|
|
100
|
+
|
|
101
|
+
load_dotenv()
|
|
102
|
+
|
|
103
|
+
client = MpesaClient(
|
|
104
|
+
consumer_key=os.getenv("MPESA_CONSUMER_KEY"),
|
|
105
|
+
consumer_secret=os.getenv("MPESA_CONSUMER_SECRET"),
|
|
106
|
+
environment="sandbox", # Switch to "production" when ready
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
response = client.stk_push(
|
|
110
|
+
business_short_code=int(os.getenv("MPESA_SHORTCODE")),
|
|
111
|
+
passkey=os.getenv("MPESA_PASSKEY"),
|
|
112
|
+
transaction_type=TransactionType.CUSTOMER_PAYBILL_ONLINE,
|
|
113
|
+
amount=250,
|
|
114
|
+
party_a=os.getenv("MPESA_PHONE_NUMBER"),
|
|
115
|
+
party_b=os.getenv("MPESA_SHORTCODE"),
|
|
116
|
+
phone_number=os.getenv("MPESA_PHONE_NUMBER"),
|
|
117
|
+
callback_url="https://yourdomain.com/mpesa/callback",
|
|
118
|
+
account_reference="Order-001",
|
|
119
|
+
transaction_desc="Payment for order",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if response.is_successful():
|
|
123
|
+
print("Request accepted:", response.CheckoutRequestID)
|
|
124
|
+
else:
|
|
125
|
+
print("Error:", response.error_message())
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 3. Handle the payment callback
|
|
129
|
+
|
|
130
|
+
The client exposes `process_*` methods that validate and deserialize incoming Safaricom payloads into typed Pydantic objects — no manual dict parsing required.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
# Example: FastAPI callback handler
|
|
134
|
+
from fastapi import FastAPI, Request
|
|
135
|
+
from fastapi.responses import JSONResponse
|
|
136
|
+
|
|
137
|
+
app = FastAPI()
|
|
138
|
+
|
|
139
|
+
@app.post("/mpesa/callback")
|
|
140
|
+
async def mpesa_callback(request: Request):
|
|
141
|
+
payload = await request.json()
|
|
142
|
+
|
|
143
|
+
# Validates the payload and returns a typed StkPushSimulateCallback object
|
|
144
|
+
callback = client.process_stk_callback(payload)
|
|
145
|
+
|
|
146
|
+
if callback.is_successful()
|
|
147
|
+
metadata = callback.Body.stkCallback.CallbackMetadata
|
|
148
|
+
print(f"Payment confirmed — Receipt: {metadata}")
|
|
149
|
+
else:
|
|
150
|
+
print(f"Payment failed: {callback.Body.stkCallback.ResultDesc}")
|
|
151
|
+
|
|
152
|
+
return JSONResponse({"ResultCode": 0, "ResultDesc": "Accepted"})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
All `process_*` methods follow the same pattern — pass in the raw JSON payload, get back a validated object:
|
|
156
|
+
|
|
157
|
+
| Method | Returns |
|
|
158
|
+
|--------|---------|
|
|
159
|
+
| `client.process_stk_callback(payload)` | `StkPushSimulateCallback` |
|
|
160
|
+
| `client.process_stk_query_callback(payload)` | `StkPushQueryResponse` |
|
|
161
|
+
| `client.process_b2c_callback(payload)` | `B2CResultCallback` |
|
|
162
|
+
| `client.process_account_balance_callback(payload)` | `AccountBalanceResultCallback` |
|
|
163
|
+
| `client.process_account_balance_timeout(payload)` | `AccountBalanceTimeoutCallback` |
|
|
164
|
+
| `client.process_transcations_callback(payload)` | `TransactionStatusResultCallback` |
|
|
165
|
+
| `client.process_reversal_callback(payload)` | `ReversalResultCallback` |
|
|
166
|
+
| `client.process_tax_remittance_callback(payload)` | `TaxRemittanceResultCallback` |
|
|
167
|
+
| `client.process_dynamic_qr_code_callback(payload)` | `DynamicQRGenerateResponse` |
|
|
168
|
+
| `client.process_b2b_callback(payload)` | `B2BExpressCheckoutCallback` |
|
|
169
|
+
| `client.process_bill_manager_callback(payload)` | `BillManagerPaymentNotificationRequest` |
|
|
170
|
+
| `client.process_ratiba_service_callback(payload)` | `StandingOrderCallback` |
|
|
171
|
+
|
|
172
|
+
### 4. Error handling
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from mpesakit.errors import MpesaApiException
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
response = client.stk_push(...)
|
|
179
|
+
except MpesaApiException as e:
|
|
180
|
+
err = e.error
|
|
181
|
+
print(f"Code: {err.error_code}") # e.g. AUTH_INVALID_CREDENTIALS
|
|
182
|
+
print(f"Message: {err.error_message}") # Human-readable description
|
|
183
|
+
print(f"HTTP status: {err.status_code}")
|
|
184
|
+
print(f"Request ID: {err.request_id}")
|
|
185
|
+
except Exception as exc:
|
|
186
|
+
print(f"Unexpected error: {exc}")
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## More Examples
|
|
192
|
+
|
|
193
|
+
### B2C — Send money to a customer
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from mpesakit.b2c import B2CCommandIDType
|
|
197
|
+
|
|
198
|
+
response = client.b2c.send_payment(
|
|
199
|
+
originator_conversation_id="ocid-1234-5678",
|
|
200
|
+
initiator_name="your_initiator_name",
|
|
201
|
+
security_credential="your_encrypted_security_credential",
|
|
202
|
+
command_id=B2CCommandIDType.BusinessPayment,
|
|
203
|
+
amount=1500,
|
|
204
|
+
party_a="600999", # Your bulk disbursement shortcode
|
|
205
|
+
party_b="254712345678", # Recipient phone number (normalized by SDK)
|
|
206
|
+
remarks="Refund for order 042",
|
|
207
|
+
queue_timeout_url="https://yourdomain.com/mpesa/timeout",
|
|
208
|
+
result_url="https://yourdomain.com/mpesa/result",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if response.is_successful():
|
|
212
|
+
print("Payout sent:", response.ResponseDescription)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
> **Note:** B2C in production requires a Bulk Disbursement Account from Safaricom — a standard PayBill or Till will not work. See the [B2C docs](https://mpesakit.dev/b2c) for details.
|
|
216
|
+
|
|
217
|
+
### STK Query — Check a push status
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
response = client.stk_query(
|
|
221
|
+
business_short_code=int(os.getenv("MPESA_SHORTCODE")),
|
|
222
|
+
passkey=os.getenv("MPESA_PASSKEY"),
|
|
223
|
+
checkout_request_id="ws_CO_191220191020363925",
|
|
224
|
+
)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Switching to production
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
client = MpesaClient(
|
|
231
|
+
consumer_key="...",
|
|
232
|
+
consumer_secret="...",
|
|
233
|
+
environment="production", # That's all it takes
|
|
234
|
+
)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Supported APIs
|
|
240
|
+
|
|
241
|
+
| API | Status | Description |
|
|
242
|
+
|-----|--------|-------------|
|
|
243
|
+
| **STK Push** | ✅ Ready | Prompt a customer to enter their M-Pesa PIN to pay |
|
|
244
|
+
| **STK Query** | ✅ Ready | Check the status of an STK Push request |
|
|
245
|
+
| **C2B Payments** | ✅ Ready | Receive payments from customers via paybill or till |
|
|
246
|
+
| **B2C Payments** | ✅ Ready | Send money to customers or staff |
|
|
247
|
+
| **B2C Account Top-up** | ✅ Ready | Top up B2C utility accounts |
|
|
248
|
+
| **Business Paybill** | ✅ Ready | Business-to-business paybill transfers |
|
|
249
|
+
| **Business BuyGoods** | ✅ Ready | Business-to-business till transfers |
|
|
250
|
+
| **Token Management** | ✅ Ready | Automatic OAuth2 token handling — no manual refresh needed |
|
|
251
|
+
| **Account Balance** | ✅ Ready | Query your M-Pesa account balance |
|
|
252
|
+
| **Transaction Status** | ✅ Ready | Look up the status of any past transaction |
|
|
253
|
+
| **Transaction Reversal** | ✅ Ready | Reverse erroneous transactions |
|
|
254
|
+
| **Dynamic QR** | ✅ Ready | Generate QR codes for M-Pesa payments |
|
|
255
|
+
| **Tax Remittance** | ✅ Ready | Submit tax remittances via M-Pesa |
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Security Best Practices
|
|
260
|
+
|
|
261
|
+
- **Never commit credentials** to version control — use environment variables or a secrets manager
|
|
262
|
+
- **Validate callbacks** using `is_mpesa_ip_allowed` to restrict requests to known Safaricom IP ranges
|
|
263
|
+
- **Use HTTPS** for all callback URLs — Safaricom will not deliver to plain HTTP in production
|
|
264
|
+
- **Log transaction IDs** (`OriginatorConversationID`, `ConversationID`) for reconciliation and dispute resolution
|
|
265
|
+
- **Persist callback payloads** before returning an acknowledgement, to protect against processing failures
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Full Documentation
|
|
270
|
+
|
|
271
|
+
API reference, webhook guides, and production checklist: **[mpesakit.dev](https://mpesakit.dev)**
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Contributing
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
git clone https://github.com/Byte-Barn/mpesakit.git
|
|
279
|
+
cd mpesakit
|
|
280
|
+
|
|
281
|
+
python -m venv venv
|
|
282
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
283
|
+
|
|
284
|
+
pip install -e ".[dev]"
|
|
285
|
+
pytest tests/unit
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Ways to contribute:
|
|
289
|
+
- **Report bugs** — [GitHub Issues](https://github.com/Byte-Barn/mpesakit/issues)
|
|
290
|
+
- **Suggest features** — [GitHub Discussions](https://github.com/Byte-Barn/mpesakit/discussions)
|
|
291
|
+
- **Submit a PR** — please include tests and update docs for any API changes
|
|
292
|
+
- **Join the community** — [Discord](https://discord.gg/hNxTew523E)
|
|
293
|
+
|
|
294
|
+
Please follow PEP 8 and include type hints in new code.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Support
|
|
299
|
+
|
|
300
|
+
- 📖 Docs: [mpesakit.dev](https://mpesakit.dev)
|
|
301
|
+
- 🐛 Issues: [github.com/Byte-Barn/mpesakit/issues](https://github.com/Byte-Barn/mpesakit/issues)
|
|
302
|
+
- 💬 Discussions: [github.com/Byte-Barn/mpesakit/discussions](https://github.com/Byte-Barn/mpesakit/discussions)
|
|
303
|
+
- 📧 Email: johnmkagunda@gmail.com
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## License
|
|
308
|
+
|
|
309
|
+
[Apache 2.0](LICENSE) — free for commercial and private use.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
<div align="center">
|
|
314
|
+
|
|
315
|
+
**Made with ❤️ for the Kenyan developer community**
|
|
316
|
+
|
|
317
|
+
[⭐ Star this repo](https://github.com/Byte-Barn/mpesakit) · [🐛 Report a bug](https://github.com/Byte-Barn/mpesakit/issues) · [💡 Request a feature](https://github.com/Byte-Barn/mpesakit/issues/new)
|
|
318
|
+
|
|
319
|
+
*Built on the shoulders of [`Arlus/mpesa-py`](https://github.com/Arlus/mpesa-py)*
|
|
320
|
+
|
|
321
|
+
</div>
|
mpesakit-2.2.0/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# mpesakit
|
|
2
|
+
|
|
3
|
+
> ⚡ Effortless M-Pesa integration using Safaricom's Daraja API — built for developers, by developers.
|
|
4
|
+
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
|
+
[](https://pypi.org/project/mpesakit)
|
|
8
|
+
[](https://pepy.tech/project/mpesakit)
|
|
9
|
+
[](https://github.com/Byte-Barn/mpesakit/actions)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Integrating Safaricom's Daraja API from scratch means wrestling with OAuth2 token rotation, security credential encryption, inconsistent sandbox vs. production endpoints, and documentation that rarely connects end-to-end.
|
|
14
|
+
|
|
15
|
+
**`mpesakit`** handles all of that. Add your credentials, call a method, move on.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install mpesakit
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Set your credentials
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export MPESA_CONSUMER_KEY="your_consumer_key"
|
|
33
|
+
export MPESA_CONSUMER_SECRET="your_consumer_secret"
|
|
34
|
+
export MPESA_SHORTCODE="your_shortcode"
|
|
35
|
+
export MPESA_PASSKEY="your_lipa_na_mpesa_passkey"
|
|
36
|
+
export MPESA_PHONE_NUMBER="254712345678"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. Trigger an STK Push
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import os
|
|
43
|
+
from dotenv import load_dotenv
|
|
44
|
+
from mpesakit import MpesaClient
|
|
45
|
+
from mpesakit.mpesa_express import TransactionType
|
|
46
|
+
|
|
47
|
+
load_dotenv()
|
|
48
|
+
|
|
49
|
+
client = MpesaClient(
|
|
50
|
+
consumer_key=os.getenv("MPESA_CONSUMER_KEY"),
|
|
51
|
+
consumer_secret=os.getenv("MPESA_CONSUMER_SECRET"),
|
|
52
|
+
environment="sandbox", # Switch to "production" when ready
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
response = client.stk_push(
|
|
56
|
+
business_short_code=int(os.getenv("MPESA_SHORTCODE")),
|
|
57
|
+
passkey=os.getenv("MPESA_PASSKEY"),
|
|
58
|
+
transaction_type=TransactionType.CUSTOMER_PAYBILL_ONLINE,
|
|
59
|
+
amount=250,
|
|
60
|
+
party_a=os.getenv("MPESA_PHONE_NUMBER"),
|
|
61
|
+
party_b=os.getenv("MPESA_SHORTCODE"),
|
|
62
|
+
phone_number=os.getenv("MPESA_PHONE_NUMBER"),
|
|
63
|
+
callback_url="https://yourdomain.com/mpesa/callback",
|
|
64
|
+
account_reference="Order-001",
|
|
65
|
+
transaction_desc="Payment for order",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if response.is_successful():
|
|
69
|
+
print("Request accepted:", response.CheckoutRequestID)
|
|
70
|
+
else:
|
|
71
|
+
print("Error:", response.error_message())
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 3. Handle the payment callback
|
|
75
|
+
|
|
76
|
+
The client exposes `process_*` methods that validate and deserialize incoming Safaricom payloads into typed Pydantic objects — no manual dict parsing required.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
# Example: FastAPI callback handler
|
|
80
|
+
from fastapi import FastAPI, Request
|
|
81
|
+
from fastapi.responses import JSONResponse
|
|
82
|
+
|
|
83
|
+
app = FastAPI()
|
|
84
|
+
|
|
85
|
+
@app.post("/mpesa/callback")
|
|
86
|
+
async def mpesa_callback(request: Request):
|
|
87
|
+
payload = await request.json()
|
|
88
|
+
|
|
89
|
+
# Validates the payload and returns a typed StkPushSimulateCallback object
|
|
90
|
+
callback = client.process_stk_callback(payload)
|
|
91
|
+
|
|
92
|
+
if callback.is_successful()
|
|
93
|
+
metadata = callback.Body.stkCallback.CallbackMetadata
|
|
94
|
+
print(f"Payment confirmed — Receipt: {metadata}")
|
|
95
|
+
else:
|
|
96
|
+
print(f"Payment failed: {callback.Body.stkCallback.ResultDesc}")
|
|
97
|
+
|
|
98
|
+
return JSONResponse({"ResultCode": 0, "ResultDesc": "Accepted"})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
All `process_*` methods follow the same pattern — pass in the raw JSON payload, get back a validated object:
|
|
102
|
+
|
|
103
|
+
| Method | Returns |
|
|
104
|
+
|--------|---------|
|
|
105
|
+
| `client.process_stk_callback(payload)` | `StkPushSimulateCallback` |
|
|
106
|
+
| `client.process_stk_query_callback(payload)` | `StkPushQueryResponse` |
|
|
107
|
+
| `client.process_b2c_callback(payload)` | `B2CResultCallback` |
|
|
108
|
+
| `client.process_account_balance_callback(payload)` | `AccountBalanceResultCallback` |
|
|
109
|
+
| `client.process_account_balance_timeout(payload)` | `AccountBalanceTimeoutCallback` |
|
|
110
|
+
| `client.process_transcations_callback(payload)` | `TransactionStatusResultCallback` |
|
|
111
|
+
| `client.process_reversal_callback(payload)` | `ReversalResultCallback` |
|
|
112
|
+
| `client.process_tax_remittance_callback(payload)` | `TaxRemittanceResultCallback` |
|
|
113
|
+
| `client.process_dynamic_qr_code_callback(payload)` | `DynamicQRGenerateResponse` |
|
|
114
|
+
| `client.process_b2b_callback(payload)` | `B2BExpressCheckoutCallback` |
|
|
115
|
+
| `client.process_bill_manager_callback(payload)` | `BillManagerPaymentNotificationRequest` |
|
|
116
|
+
| `client.process_ratiba_service_callback(payload)` | `StandingOrderCallback` |
|
|
117
|
+
|
|
118
|
+
### 4. Error handling
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from mpesakit.errors import MpesaApiException
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
response = client.stk_push(...)
|
|
125
|
+
except MpesaApiException as e:
|
|
126
|
+
err = e.error
|
|
127
|
+
print(f"Code: {err.error_code}") # e.g. AUTH_INVALID_CREDENTIALS
|
|
128
|
+
print(f"Message: {err.error_message}") # Human-readable description
|
|
129
|
+
print(f"HTTP status: {err.status_code}")
|
|
130
|
+
print(f"Request ID: {err.request_id}")
|
|
131
|
+
except Exception as exc:
|
|
132
|
+
print(f"Unexpected error: {exc}")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## More Examples
|
|
138
|
+
|
|
139
|
+
### B2C — Send money to a customer
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from mpesakit.b2c import B2CCommandIDType
|
|
143
|
+
|
|
144
|
+
response = client.b2c.send_payment(
|
|
145
|
+
originator_conversation_id="ocid-1234-5678",
|
|
146
|
+
initiator_name="your_initiator_name",
|
|
147
|
+
security_credential="your_encrypted_security_credential",
|
|
148
|
+
command_id=B2CCommandIDType.BusinessPayment,
|
|
149
|
+
amount=1500,
|
|
150
|
+
party_a="600999", # Your bulk disbursement shortcode
|
|
151
|
+
party_b="254712345678", # Recipient phone number (normalized by SDK)
|
|
152
|
+
remarks="Refund for order 042",
|
|
153
|
+
queue_timeout_url="https://yourdomain.com/mpesa/timeout",
|
|
154
|
+
result_url="https://yourdomain.com/mpesa/result",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if response.is_successful():
|
|
158
|
+
print("Payout sent:", response.ResponseDescription)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
> **Note:** B2C in production requires a Bulk Disbursement Account from Safaricom — a standard PayBill or Till will not work. See the [B2C docs](https://mpesakit.dev/b2c) for details.
|
|
162
|
+
|
|
163
|
+
### STK Query — Check a push status
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
response = client.stk_query(
|
|
167
|
+
business_short_code=int(os.getenv("MPESA_SHORTCODE")),
|
|
168
|
+
passkey=os.getenv("MPESA_PASSKEY"),
|
|
169
|
+
checkout_request_id="ws_CO_191220191020363925",
|
|
170
|
+
)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Switching to production
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
client = MpesaClient(
|
|
177
|
+
consumer_key="...",
|
|
178
|
+
consumer_secret="...",
|
|
179
|
+
environment="production", # That's all it takes
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Supported APIs
|
|
186
|
+
|
|
187
|
+
| API | Status | Description |
|
|
188
|
+
|-----|--------|-------------|
|
|
189
|
+
| **STK Push** | ✅ Ready | Prompt a customer to enter their M-Pesa PIN to pay |
|
|
190
|
+
| **STK Query** | ✅ Ready | Check the status of an STK Push request |
|
|
191
|
+
| **C2B Payments** | ✅ Ready | Receive payments from customers via paybill or till |
|
|
192
|
+
| **B2C Payments** | ✅ Ready | Send money to customers or staff |
|
|
193
|
+
| **B2C Account Top-up** | ✅ Ready | Top up B2C utility accounts |
|
|
194
|
+
| **Business Paybill** | ✅ Ready | Business-to-business paybill transfers |
|
|
195
|
+
| **Business BuyGoods** | ✅ Ready | Business-to-business till transfers |
|
|
196
|
+
| **Token Management** | ✅ Ready | Automatic OAuth2 token handling — no manual refresh needed |
|
|
197
|
+
| **Account Balance** | ✅ Ready | Query your M-Pesa account balance |
|
|
198
|
+
| **Transaction Status** | ✅ Ready | Look up the status of any past transaction |
|
|
199
|
+
| **Transaction Reversal** | ✅ Ready | Reverse erroneous transactions |
|
|
200
|
+
| **Dynamic QR** | ✅ Ready | Generate QR codes for M-Pesa payments |
|
|
201
|
+
| **Tax Remittance** | ✅ Ready | Submit tax remittances via M-Pesa |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Security Best Practices
|
|
206
|
+
|
|
207
|
+
- **Never commit credentials** to version control — use environment variables or a secrets manager
|
|
208
|
+
- **Validate callbacks** using `is_mpesa_ip_allowed` to restrict requests to known Safaricom IP ranges
|
|
209
|
+
- **Use HTTPS** for all callback URLs — Safaricom will not deliver to plain HTTP in production
|
|
210
|
+
- **Log transaction IDs** (`OriginatorConversationID`, `ConversationID`) for reconciliation and dispute resolution
|
|
211
|
+
- **Persist callback payloads** before returning an acknowledgement, to protect against processing failures
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Full Documentation
|
|
216
|
+
|
|
217
|
+
API reference, webhook guides, and production checklist: **[mpesakit.dev](https://mpesakit.dev)**
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Contributing
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
git clone https://github.com/Byte-Barn/mpesakit.git
|
|
225
|
+
cd mpesakit
|
|
226
|
+
|
|
227
|
+
python -m venv venv
|
|
228
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
229
|
+
|
|
230
|
+
pip install -e ".[dev]"
|
|
231
|
+
pytest tests/unit
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Ways to contribute:
|
|
235
|
+
- **Report bugs** — [GitHub Issues](https://github.com/Byte-Barn/mpesakit/issues)
|
|
236
|
+
- **Suggest features** — [GitHub Discussions](https://github.com/Byte-Barn/mpesakit/discussions)
|
|
237
|
+
- **Submit a PR** — please include tests and update docs for any API changes
|
|
238
|
+
- **Join the community** — [Discord](https://discord.gg/hNxTew523E)
|
|
239
|
+
|
|
240
|
+
Please follow PEP 8 and include type hints in new code.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Support
|
|
245
|
+
|
|
246
|
+
- 📖 Docs: [mpesakit.dev](https://mpesakit.dev)
|
|
247
|
+
- 🐛 Issues: [github.com/Byte-Barn/mpesakit/issues](https://github.com/Byte-Barn/mpesakit/issues)
|
|
248
|
+
- 💬 Discussions: [github.com/Byte-Barn/mpesakit/discussions](https://github.com/Byte-Barn/mpesakit/discussions)
|
|
249
|
+
- 📧 Email: johnmkagunda@gmail.com
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
[Apache 2.0](LICENSE) — free for commercial and private use.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
<div align="center">
|
|
260
|
+
|
|
261
|
+
**Made with ❤️ for the Kenyan developer community**
|
|
262
|
+
|
|
263
|
+
[⭐ Star this repo](https://github.com/Byte-Barn/mpesakit) · [🐛 Report a bug](https://github.com/Byte-Barn/mpesakit/issues) · [💡 Request a feature](https://github.com/Byte-Barn/mpesakit/issues/new)
|
|
264
|
+
|
|
265
|
+
*Built on the shoulders of [`Arlus/mpesa-py`](https://github.com/Arlus/mpesa-py)*
|
|
266
|
+
|
|
267
|
+
</div>
|