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.
Files changed (80) hide show
  1. {mpesakit-2.1.1 → mpesakit-2.2.0}/AUTHORS.md +2 -0
  2. mpesakit-2.2.0/PKG-INFO +321 -0
  3. mpesakit-2.2.0/README.md +267 -0
  4. mpesakit-2.2.0/mpesakit/__init__.py +5 -0
  5. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_client.py +193 -101
  6. mpesakit-2.2.0/mpesakit/services/__init__.py +36 -0
  7. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/b2c.py +87 -3
  8. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/balance.py +48 -2
  9. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/bill.py +118 -2
  10. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/c2b.py +40 -2
  11. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/dynamic_qr.py +44 -2
  12. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/express.py +78 -2
  13. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/ratiba.py +56 -2
  14. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/reversal.py +46 -2
  15. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/tax.py +48 -2
  16. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/transaction.py +62 -2
  17. mpesakit-2.1.1/PKG-INFO +0 -217
  18. mpesakit-2.1.1/README.md +0 -163
  19. mpesakit-2.1.1/mpesakit/__init__.py +0 -5
  20. mpesakit-2.1.1/mpesakit/services/__init__.py +0 -25
  21. {mpesakit-2.1.1 → mpesakit-2.2.0}/.gitignore +0 -0
  22. {mpesakit-2.1.1 → mpesakit-2.2.0}/LICENSE +0 -0
  23. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/account_balance/__init__.py +0 -0
  24. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/account_balance/account_balance.py +0 -0
  25. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/account_balance/schemas.py +0 -0
  26. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/auth/__init__.py +0 -0
  27. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/auth/access_token.py +0 -0
  28. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/auth/token_manager.py +0 -0
  29. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2b_express_checkout/__init__.py +0 -0
  30. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2b_express_checkout/b2b_express_checkout.py +0 -0
  31. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2b_express_checkout/schemas.py +0 -0
  32. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2c/__init__.py +0 -0
  33. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2c/b2c.py +0 -0
  34. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2c/schemas.py +0 -0
  35. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2c_account_top_up/__init__.py +0 -0
  36. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2c_account_top_up/b2c_account_top_up.py +0 -0
  37. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/b2c_account_top_up/schemas.py +0 -0
  38. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/bill_manager/__init__.py +0 -0
  39. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/bill_manager/bill_manager.py +0 -0
  40. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/bill_manager/schemas.py +0 -0
  41. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/business_buy_goods/__init__.py +0 -0
  42. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/business_buy_goods/business_buy_goods.py +0 -0
  43. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/business_buy_goods/schemas.py +0 -0
  44. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/business_paybill/__init__.py +0 -0
  45. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/business_paybill/business_paybill.py +0 -0
  46. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/business_paybill/schemas.py +0 -0
  47. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/c2b/__init__.py +0 -0
  48. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/c2b/c2b.py +0 -0
  49. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/c2b/schemas.py +0 -0
  50. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/dynamic_qr_code/__init__.py +0 -0
  51. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/dynamic_qr_code/dynamic_qr_code.py +0 -0
  52. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/dynamic_qr_code/schemas.py +0 -0
  53. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/errors.py +0 -0
  54. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/http_client/__init__.py +0 -0
  55. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/http_client/http_client.py +0 -0
  56. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/http_client/mpesa_async_http_client.py +0 -0
  57. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/http_client/mpesa_http_client.py +0 -0
  58. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_express/__init__.py +0 -0
  59. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_express/schemas.py +0 -0
  60. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_express/stk_push.py +0 -0
  61. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_ratiba/__init__.py +0 -0
  62. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_ratiba/mpesa_ratiba.py +0 -0
  63. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/mpesa_ratiba/schemas.py +0 -0
  64. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/reversal/__init__.py +0 -0
  65. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/reversal/reversal.py +0 -0
  66. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/reversal/schemas.py +0 -0
  67. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/security/__init__.py +0 -0
  68. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/security/certs/ProductionCertificate.cer +0 -0
  69. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/security/certs/SandboxCertificate.cer +0 -0
  70. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/security/get_credential.py +0 -0
  71. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/security/ip_whitelist.py +0 -0
  72. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/services/b2b.py +0 -0
  73. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/tax_remittance/__init__.py +0 -0
  74. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/tax_remittance/schemas.py +0 -0
  75. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/tax_remittance/tax_remittance.py +0 -0
  76. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/transaction_status/__init__.py +0 -0
  77. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/transaction_status/schemas.py +0 -0
  78. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/transaction_status/transaction_status.py +0 -0
  79. {mpesakit-2.1.1 → mpesakit-2.2.0}/mpesakit/utils/phone.py +0 -0
  80. {mpesakit-2.1.1 → mpesakit-2.2.0}/pyproject.toml +0 -0
@@ -1,3 +1,5 @@
1
1
  # Authors
2
2
 
3
3
  - **John Kagunda** – [@rafaeljohn9](mailto:johnmkagunda@gmail.com)
4
+
5
+ - **Bramuel Barack** – [@watersRand](mailto:bramwelbarack89@gmail.com)
@@ -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
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
60
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0)
61
+ [![PyPI version](https://img.shields.io/pypi/v/mpesakit.svg)](https://pypi.org/project/mpesakit)
62
+ [![Downloads](https://pepy.tech/badge/mpesakit)](https://pepy.tech/project/mpesakit)
63
+ [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](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>
@@ -0,0 +1,267 @@
1
+ # mpesakit
2
+
3
+ > ⚡ Effortless M-Pesa integration using Safaricom's Daraja API — built for developers, by developers.
4
+
5
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
6
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0)
7
+ [![PyPI version](https://img.shields.io/pypi/v/mpesakit.svg)](https://pypi.org/project/mpesakit)
8
+ [![Downloads](https://pepy.tech/badge/mpesakit)](https://pepy.tech/project/mpesakit)
9
+ [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](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>
@@ -0,0 +1,5 @@
1
+ from .mpesa_client import MpesaClient, AsyncMpesaClient
2
+
3
+ __all__ = ["MpesaClient", "AsyncMpesaClient"]
4
+
5
+ __version__ = "2.0.0"