mpesakit 2.0.0__tar.gz → 2.0.2__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.0.0 → mpesakit-2.0.2}/PKG-INFO +35 -49
- {mpesakit-2.0.0 → mpesakit-2.0.2}/README.md +31 -48
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/account_balance/account_balance.py +1 -1
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/account_balance/schemas.py +3 -3
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/b2b_express_checkout/schemas.py +1 -1
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/b2c/b2c.py +1 -1
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/b2c/schemas.py +3 -3
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/b2c_account_top_up/schemas.py +4 -4
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/business_buy_goods/business_buy_goods.py +1 -1
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/business_buy_goods/schemas.py +2 -2
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/business_paybill/business_paybill.py +1 -1
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/business_paybill/schemas.py +3 -3
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/c2b/c2b.py +1 -1
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/c2b/schemas.py +2 -2
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/dynamic_qr_code/dynamic_qr_code.py +1 -1
- mpesakit-2.0.2/mpesakit/http_client/__init__.py +5 -0
- mpesakit-2.0.2/mpesakit/http_client/http_client.py +53 -0
- mpesakit-2.0.2/mpesakit/http_client/mpesa_async_http_client.py +159 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/mpesa_express/schemas.py +4 -4
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/mpesa_express/stk_push.py +1 -1
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/reversal/reversal.py +1 -1
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/reversal/schemas.py +2 -2
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/services/transaction.py +15 -9
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/tax_remittance/schemas.py +2 -2
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/tax_remittance/tax_remittance.py +1 -1
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/transaction_status/schemas.py +4 -4
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/transaction_status/transaction_status.py +1 -1
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/utils/phone.py +1 -1
- {mpesakit-2.0.0 → mpesakit-2.0.2}/pyproject.toml +7 -3
- mpesakit-2.0.0/mpesakit/http_client/__init__.py +0 -4
- mpesakit-2.0.0/mpesakit/http_client/http_client.py +0 -28
- {mpesakit-2.0.0 → mpesakit-2.0.2}/.gitignore +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/AUTHORS.md +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/LICENSE +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/account_balance/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/auth/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/auth/access_token.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/auth/token_manager.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/b2b_express_checkout/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/b2b_express_checkout/b2b_express_checkout.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/b2c/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/b2c_account_top_up/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/b2c_account_top_up/b2c_account_top_up.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/bill_manager/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/bill_manager/bill_manager.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/bill_manager/schemas.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/business_buy_goods/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/business_paybill/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/c2b/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/dynamic_qr_code/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/dynamic_qr_code/schemas.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/errors.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/http_client/mpesa_http_client.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/mpesa_client.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/mpesa_express/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/mpesa_ratiba/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/mpesa_ratiba/mpesa_ratiba.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/mpesa_ratiba/schemas.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/reversal/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/security/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/security/certs/ProductionCertificate.cer +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/security/certs/SandboxCertificate.cer +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/security/get_credential.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/security/ip_whitelist.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/services/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/services/b2b.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/services/b2c.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/services/balance.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/services/bill.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/services/c2b.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/services/dynamic_qr.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/services/express.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/services/ratiba.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/services/reversal.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/services/tax.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/tax_remittance/__init__.py +0 -0
- {mpesakit-2.0.0 → mpesakit-2.0.2}/mpesakit/transaction_status/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mpesakit
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: A Python SDK for integrating with M-Pesa APIs
|
|
5
5
|
Project-URL: Homepage, https://github.com/rafaeljohn9/mpesakit
|
|
6
6
|
Project-URL: Repository, https://github.com/rafaeljohn9/mpesakit
|
|
@@ -21,6 +21,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
21
21
|
Classifier: Typing :: Typed
|
|
22
22
|
Requires-Python: >=3.12
|
|
23
23
|
Requires-Dist: cryptography>=41.0.7
|
|
24
|
+
Requires-Dist: httpx<1.0.0,>=0.27.0
|
|
24
25
|
Requires-Dist: pydantic<3.0.0,>=2.10.6
|
|
25
26
|
Requires-Dist: pydantic[email]<3.0.0,>=2.10.6
|
|
26
27
|
Requires-Dist: requests<3.0.0,>=2.32.3
|
|
@@ -34,6 +35,7 @@ Requires-Dist: hatch-vcs; extra == 'dev'
|
|
|
34
35
|
Requires-Dist: hatchling; extra == 'dev'
|
|
35
36
|
Requires-Dist: mypy; extra == 'dev'
|
|
36
37
|
Requires-Dist: pytest; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-asyncio<1.0.0,>=0.23.6; extra == 'dev'
|
|
37
39
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
38
40
|
Requires-Dist: ruff; extra == 'dev'
|
|
39
41
|
Requires-Dist: types-requests; extra == 'dev'
|
|
@@ -42,18 +44,20 @@ Requires-Dist: bandit; extra == 'test'
|
|
|
42
44
|
Requires-Dist: mypy; extra == 'test'
|
|
43
45
|
Requires-Dist: pyngrok; extra == 'test'
|
|
44
46
|
Requires-Dist: pytest; extra == 'test'
|
|
47
|
+
Requires-Dist: pytest-asyncio<1.0.0,>=0.23.6; extra == 'test'
|
|
45
48
|
Requires-Dist: pytest-cov; extra == 'test'
|
|
46
49
|
Requires-Dist: types-requests; extra == 'test'
|
|
47
50
|
Description-Content-Type: text/markdown
|
|
48
51
|
|
|
49
|
-
#
|
|
52
|
+
# mpesakit
|
|
50
53
|
|
|
51
54
|
> ⚡ **Effortless M-Pesa integration** using Safaricom's Daraja API — built for developers, by developers.
|
|
52
55
|
|
|
53
56
|
[](https://www.python.org/downloads/)
|
|
54
57
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
55
|
-
|
|
58
|
+
[]()
|
|
56
59
|
|
|
60
|
+
[](https://pypi.org/project/mpesakit) [](https://pepy.tech/project/mpesakit)
|
|
57
61
|
---
|
|
58
62
|
|
|
59
63
|
## The Problem
|
|
@@ -73,7 +77,7 @@ For many developers and startups, this becomes a **huge barrier** to adopting M-
|
|
|
73
77
|
|
|
74
78
|
## The Solution
|
|
75
79
|
|
|
76
|
-
**`
|
|
80
|
+
**`mpesakit`** eliminates the complexity with a **clean, developer-friendly Python SDK** that:
|
|
77
81
|
|
|
78
82
|
- **Zero-config setup** — just add your credentials and go
|
|
79
83
|
- **Handles authentication automatically** — OAuth2, tokens, and security
|
|
@@ -86,13 +90,19 @@ For many developers and startups, this becomes a **huge barrier** to adopting M-
|
|
|
86
90
|
|
|
87
91
|
| Feature | Status | Description |
|
|
88
92
|
|---------|--------|-------------|
|
|
89
|
-
| **STK Push** | Ready |
|
|
90
|
-
| **
|
|
91
|
-
| **
|
|
92
|
-
| **
|
|
93
|
-
| **Account
|
|
94
|
-
|
|
|
95
|
-
|
|
|
93
|
+
| **STK Push (Lipa na M-Pesa)** | Ready | Initiate customer payment prompts (mpesa-express/stk-push.mdx) |
|
|
94
|
+
| **STK Query** | Ready | Query STK push/payment status (mpesa-express/stk-query.mdx) |
|
|
95
|
+
| **C2B Payments** | Ready | Customer-to-Business payments (c2b.mdx) |
|
|
96
|
+
| **B2C Payments** | Ready | Business-to-Customer payouts (b2c.mdx) |
|
|
97
|
+
| **B2C Account Top-up** | Ready | Account top-up flows for B2C (b2c-account-top-up.mdx) |
|
|
98
|
+
| **Business Paybill** | Ready | Paybill integrations for business collections (business-paybill.mdx) |
|
|
99
|
+
| **Business BuyGoods** | Ready | Till/BuyGoods integrations (business-buygoods.mdx) |
|
|
100
|
+
| **Token Management / Auth** | Ready | Automatic OAuth2 handling and auth utilities (auth.mdx) |
|
|
101
|
+
| **Account Balance** | Ready | Check account balances (account-balance.mdx) |
|
|
102
|
+
| **Transaction Reversal** | Ready | Reverse transactions (reversal.mdx) |
|
|
103
|
+
| **Transaction Status** | Ready | Query transaction status (transaction-status.mdx) |
|
|
104
|
+
| **Dynamic QR** | Ready | Generate and manage dynamic QR payments (dynamic-qr.mdx) |
|
|
105
|
+
| **Tax Remittance** | Ready | Tax remittance flows and docs (tax-remittance.mdx) |
|
|
96
106
|
|
|
97
107
|
> Built on top of [Arlus/mpesa-py](https://github.com/Arlus/mpesa-py) with ❤️ — modernized, cleaned up, and restructured for today's developer needs.
|
|
98
108
|
|
|
@@ -100,41 +110,17 @@ For many developers and startups, this becomes a **huge barrier** to adopting M-
|
|
|
100
110
|
|
|
101
111
|
## Quick Start
|
|
102
112
|
|
|
103
|
-
### Installation (
|
|
113
|
+
### Installation (already here :) )
|
|
104
114
|
|
|
105
115
|
```bash
|
|
106
|
-
pip install
|
|
116
|
+
pip install mpesakit
|
|
107
117
|
```
|
|
108
118
|
|
|
109
119
|
---
|
|
110
120
|
|
|
111
121
|
## 📖 Complete Setup Guide
|
|
112
122
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
1. Visit [developer.safaricom.co.ke](https://developer.safaricom.co.ke)
|
|
116
|
-
2. **Create account** and verify your email
|
|
117
|
-
3. **Create a new app** to get your credentials:
|
|
118
|
-
- Consumer Key
|
|
119
|
-
- Consumer Secret
|
|
120
|
-
|
|
121
|
-
### 2. Obtain Test Credentials (Sandbox)
|
|
122
|
-
|
|
123
|
-
1. Navigate to [Test Credentials Page](https://developer.safaricom.co.ke/test_credentials)
|
|
124
|
-
2. Copy the following credentials:
|
|
125
|
-
- **Shortcode** (Business number)
|
|
126
|
-
- **Initiator Name** (API operator username)
|
|
127
|
-
- **Initiator Password** (API operator password)
|
|
128
|
-
- **Security Credential** (Encrypted password)
|
|
129
|
-
|
|
130
|
-
### 3. Production Setup
|
|
131
|
-
|
|
132
|
-
For production deployment:
|
|
133
|
-
|
|
134
|
-
1. **Get Paybill/Till Number** from Safaricom
|
|
135
|
-
2. **Generate Security Credential** using Safaricom's public certificate
|
|
136
|
-
3. **Switch environment** to `"production"` in your client initialization
|
|
137
|
-
4. **Update callback URLs** to your production domain
|
|
123
|
+
- For the complete setup guide kindly check the documentation at: [https://mpesakit.dev](https://mpesakit.dev)
|
|
138
124
|
|
|
139
125
|
---
|
|
140
126
|
|
|
@@ -165,8 +151,8 @@ We welcome contributions from the community! Here's how you can help:
|
|
|
165
151
|
|
|
166
152
|
```bash
|
|
167
153
|
# Clone the repository
|
|
168
|
-
git clone https://github.com/rafaeljohn9/
|
|
169
|
-
cd
|
|
154
|
+
git clone https://github.com/rafaeljohn9/mpesakit.git
|
|
155
|
+
cd mpesakit
|
|
170
156
|
|
|
171
157
|
# Create virtual environment
|
|
172
158
|
python -m venv venv
|
|
@@ -176,7 +162,7 @@ source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
|
176
162
|
pip install -e ".[dev]"
|
|
177
163
|
|
|
178
164
|
# Run tests
|
|
179
|
-
pytest tests/
|
|
165
|
+
pytest tests/unit
|
|
180
166
|
```
|
|
181
167
|
|
|
182
168
|
### Code Standards
|
|
@@ -191,8 +177,8 @@ pytest tests/
|
|
|
191
177
|
## 📞 Support & Community
|
|
192
178
|
|
|
193
179
|
- 📖 **Documentation**: [Full API docs coming soon]
|
|
194
|
-
- 🐛 **Issues**: [GitHub Issues](https://github.com/rafaeljohn9/
|
|
195
|
-
- 💬 **Discussions**: [GitHub Discussions](https://github.com/rafaeljohn9/
|
|
180
|
+
- 🐛 **Issues**: [GitHub Issues](https://github.com/rafaeljohn9/mpesakit/issues)
|
|
181
|
+
- 💬 **Discussions**: [GitHub Discussions](https://github.com/rafaeljohn9/mpesakit/discussions)
|
|
196
182
|
- 📧 **Email**: <johnmkagunda@gmail.com>
|
|
197
183
|
|
|
198
184
|
---
|
|
@@ -203,11 +189,11 @@ This project began as a fork of the fantastic [`Arlus/mpesa-py`](https://github.
|
|
|
203
189
|
|
|
204
190
|
**What we've added:**
|
|
205
191
|
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
-
|
|
192
|
+
- **Modular architecture** for better maintainability
|
|
193
|
+
- **Developer-first design** with intuitive APIs
|
|
194
|
+
- **Comprehensive testing** suite
|
|
195
|
+
- **Better documentation** and examples
|
|
196
|
+
- **Production-ready** features and error handling
|
|
211
197
|
|
|
212
198
|
Special thanks to the original contributors and the broader Python community in Kenya.
|
|
213
199
|
|
|
@@ -223,6 +209,6 @@ Licensed under the [Apache 2.0 License](LICENSE) — free for commercial and pri
|
|
|
223
209
|
|
|
224
210
|
**Made with ❤️ for the Kenyan developer community**
|
|
225
211
|
|
|
226
|
-
[⭐ Star this repo](https://github.com/rafaeljohn9/
|
|
212
|
+
[⭐ Star this repo](https://github.com/rafaeljohn9/mpesakit) | [🐛 Report Issue](https://github.com/rafaeljohn9/mpesakit/issues) | [💡 Request Feature](https://github.com/rafaeljohn9/mpesakit/issues/new)
|
|
227
213
|
|
|
228
214
|
</div>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mpesakit
|
|
2
2
|
|
|
3
3
|
> ⚡ **Effortless M-Pesa integration** using Safaricom's Daraja API — built for developers, by developers.
|
|
4
4
|
|
|
5
5
|
[](https://www.python.org/downloads/)
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
|
-
|
|
7
|
+
[]()
|
|
8
8
|
|
|
9
|
+
[](https://pypi.org/project/mpesakit) [](https://pepy.tech/project/mpesakit)
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
## The Problem
|
|
@@ -25,7 +26,7 @@ For many developers and startups, this becomes a **huge barrier** to adopting M-
|
|
|
25
26
|
|
|
26
27
|
## The Solution
|
|
27
28
|
|
|
28
|
-
**`
|
|
29
|
+
**`mpesakit`** eliminates the complexity with a **clean, developer-friendly Python SDK** that:
|
|
29
30
|
|
|
30
31
|
- **Zero-config setup** — just add your credentials and go
|
|
31
32
|
- **Handles authentication automatically** — OAuth2, tokens, and security
|
|
@@ -38,13 +39,19 @@ For many developers and startups, this becomes a **huge barrier** to adopting M-
|
|
|
38
39
|
|
|
39
40
|
| Feature | Status | Description |
|
|
40
41
|
|---------|--------|-------------|
|
|
41
|
-
| **STK Push** | Ready |
|
|
42
|
-
| **
|
|
43
|
-
| **
|
|
44
|
-
| **
|
|
45
|
-
| **Account
|
|
46
|
-
|
|
|
47
|
-
|
|
|
42
|
+
| **STK Push (Lipa na M-Pesa)** | Ready | Initiate customer payment prompts (mpesa-express/stk-push.mdx) |
|
|
43
|
+
| **STK Query** | Ready | Query STK push/payment status (mpesa-express/stk-query.mdx) |
|
|
44
|
+
| **C2B Payments** | Ready | Customer-to-Business payments (c2b.mdx) |
|
|
45
|
+
| **B2C Payments** | Ready | Business-to-Customer payouts (b2c.mdx) |
|
|
46
|
+
| **B2C Account Top-up** | Ready | Account top-up flows for B2C (b2c-account-top-up.mdx) |
|
|
47
|
+
| **Business Paybill** | Ready | Paybill integrations for business collections (business-paybill.mdx) |
|
|
48
|
+
| **Business BuyGoods** | Ready | Till/BuyGoods integrations (business-buygoods.mdx) |
|
|
49
|
+
| **Token Management / Auth** | Ready | Automatic OAuth2 handling and auth utilities (auth.mdx) |
|
|
50
|
+
| **Account Balance** | Ready | Check account balances (account-balance.mdx) |
|
|
51
|
+
| **Transaction Reversal** | Ready | Reverse transactions (reversal.mdx) |
|
|
52
|
+
| **Transaction Status** | Ready | Query transaction status (transaction-status.mdx) |
|
|
53
|
+
| **Dynamic QR** | Ready | Generate and manage dynamic QR payments (dynamic-qr.mdx) |
|
|
54
|
+
| **Tax Remittance** | Ready | Tax remittance flows and docs (tax-remittance.mdx) |
|
|
48
55
|
|
|
49
56
|
> Built on top of [Arlus/mpesa-py](https://github.com/Arlus/mpesa-py) with ❤️ — modernized, cleaned up, and restructured for today's developer needs.
|
|
50
57
|
|
|
@@ -52,41 +59,17 @@ For many developers and startups, this becomes a **huge barrier** to adopting M-
|
|
|
52
59
|
|
|
53
60
|
## Quick Start
|
|
54
61
|
|
|
55
|
-
### Installation (
|
|
62
|
+
### Installation (already here :) )
|
|
56
63
|
|
|
57
64
|
```bash
|
|
58
|
-
pip install
|
|
65
|
+
pip install mpesakit
|
|
59
66
|
```
|
|
60
67
|
|
|
61
68
|
---
|
|
62
69
|
|
|
63
70
|
## 📖 Complete Setup Guide
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
1. Visit [developer.safaricom.co.ke](https://developer.safaricom.co.ke)
|
|
68
|
-
2. **Create account** and verify your email
|
|
69
|
-
3. **Create a new app** to get your credentials:
|
|
70
|
-
- Consumer Key
|
|
71
|
-
- Consumer Secret
|
|
72
|
-
|
|
73
|
-
### 2. Obtain Test Credentials (Sandbox)
|
|
74
|
-
|
|
75
|
-
1. Navigate to [Test Credentials Page](https://developer.safaricom.co.ke/test_credentials)
|
|
76
|
-
2. Copy the following credentials:
|
|
77
|
-
- **Shortcode** (Business number)
|
|
78
|
-
- **Initiator Name** (API operator username)
|
|
79
|
-
- **Initiator Password** (API operator password)
|
|
80
|
-
- **Security Credential** (Encrypted password)
|
|
81
|
-
|
|
82
|
-
### 3. Production Setup
|
|
83
|
-
|
|
84
|
-
For production deployment:
|
|
85
|
-
|
|
86
|
-
1. **Get Paybill/Till Number** from Safaricom
|
|
87
|
-
2. **Generate Security Credential** using Safaricom's public certificate
|
|
88
|
-
3. **Switch environment** to `"production"` in your client initialization
|
|
89
|
-
4. **Update callback URLs** to your production domain
|
|
72
|
+
- For the complete setup guide kindly check the documentation at: [https://mpesakit.dev](https://mpesakit.dev)
|
|
90
73
|
|
|
91
74
|
---
|
|
92
75
|
|
|
@@ -117,8 +100,8 @@ We welcome contributions from the community! Here's how you can help:
|
|
|
117
100
|
|
|
118
101
|
```bash
|
|
119
102
|
# Clone the repository
|
|
120
|
-
git clone https://github.com/rafaeljohn9/
|
|
121
|
-
cd
|
|
103
|
+
git clone https://github.com/rafaeljohn9/mpesakit.git
|
|
104
|
+
cd mpesakit
|
|
122
105
|
|
|
123
106
|
# Create virtual environment
|
|
124
107
|
python -m venv venv
|
|
@@ -128,7 +111,7 @@ source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
|
128
111
|
pip install -e ".[dev]"
|
|
129
112
|
|
|
130
113
|
# Run tests
|
|
131
|
-
pytest tests/
|
|
114
|
+
pytest tests/unit
|
|
132
115
|
```
|
|
133
116
|
|
|
134
117
|
### Code Standards
|
|
@@ -143,8 +126,8 @@ pytest tests/
|
|
|
143
126
|
## 📞 Support & Community
|
|
144
127
|
|
|
145
128
|
- 📖 **Documentation**: [Full API docs coming soon]
|
|
146
|
-
- 🐛 **Issues**: [GitHub Issues](https://github.com/rafaeljohn9/
|
|
147
|
-
- 💬 **Discussions**: [GitHub Discussions](https://github.com/rafaeljohn9/
|
|
129
|
+
- 🐛 **Issues**: [GitHub Issues](https://github.com/rafaeljohn9/mpesakit/issues)
|
|
130
|
+
- 💬 **Discussions**: [GitHub Discussions](https://github.com/rafaeljohn9/mpesakit/discussions)
|
|
148
131
|
- 📧 **Email**: <johnmkagunda@gmail.com>
|
|
149
132
|
|
|
150
133
|
---
|
|
@@ -155,11 +138,11 @@ This project began as a fork of the fantastic [`Arlus/mpesa-py`](https://github.
|
|
|
155
138
|
|
|
156
139
|
**What we've added:**
|
|
157
140
|
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
141
|
+
- **Modular architecture** for better maintainability
|
|
142
|
+
- **Developer-first design** with intuitive APIs
|
|
143
|
+
- **Comprehensive testing** suite
|
|
144
|
+
- **Better documentation** and examples
|
|
145
|
+
- **Production-ready** features and error handling
|
|
163
146
|
|
|
164
147
|
Special thanks to the original contributors and the broader Python community in Kenya.
|
|
165
148
|
|
|
@@ -175,6 +158,6 @@ Licensed under the [Apache 2.0 License](LICENSE) — free for commercial and pri
|
|
|
175
158
|
|
|
176
159
|
**Made with ❤️ for the Kenyan developer community**
|
|
177
160
|
|
|
178
|
-
[⭐ Star this repo](https://github.com/rafaeljohn9/
|
|
161
|
+
[⭐ Star this repo](https://github.com/rafaeljohn9/mpesakit) | [🐛 Report Issue](https://github.com/rafaeljohn9/mpesakit/issues) | [💡 Request Feature](https://github.com/rafaeljohn9/mpesakit/issues/new)
|
|
179
162
|
|
|
180
163
|
</div>
|
|
@@ -43,5 +43,5 @@ class AccountBalance(BaseModel):
|
|
|
43
43
|
"Authorization": f"Bearer {self.token_manager.get_token()}",
|
|
44
44
|
"Content-Type": "application/json",
|
|
45
45
|
}
|
|
46
|
-
response_data = self.http_client.post(url, json=
|
|
46
|
+
response_data = self.http_client.post(url, json=request.model_dump(by_alias=True), headers=headers)
|
|
47
47
|
return AccountBalanceResponse(**response_data)
|
|
@@ -136,7 +136,7 @@ class AccountBalanceResultMetadata(BaseModel):
|
|
|
136
136
|
"""Metadata for Account Balance result notification."""
|
|
137
137
|
|
|
138
138
|
ResultType: int = Field(..., description="Type of result (0=Success, 1=Waiting).")
|
|
139
|
-
ResultCode: int = Field(..., description="Result code (0=Success).")
|
|
139
|
+
ResultCode: int | str = Field(..., description="Result code (0=Success).")
|
|
140
140
|
ResultDesc: str = Field(..., description="Result description.")
|
|
141
141
|
OriginatorConversationID: str = Field(
|
|
142
142
|
..., description="Originator conversation ID."
|
|
@@ -238,7 +238,7 @@ class AccountBalanceResultCallback(BaseModel):
|
|
|
238
238
|
class AccountBalanceResultCallbackResponse(BaseModel):
|
|
239
239
|
"""Schema for response to Account Balance result callback."""
|
|
240
240
|
|
|
241
|
-
ResultCode: int = Field(
|
|
241
|
+
ResultCode: int | str = Field(
|
|
242
242
|
default=0, description="Result code (0=Success, other=Failure)."
|
|
243
243
|
)
|
|
244
244
|
ResultDesc: str = Field(
|
|
@@ -279,7 +279,7 @@ class AccountBalanceTimeoutCallback(BaseModel):
|
|
|
279
279
|
class AccountBalanceTimeoutCallbackResponse(BaseModel):
|
|
280
280
|
"""Schema for response to Account Balance timeout callback."""
|
|
281
281
|
|
|
282
|
-
ResultCode: int = Field(
|
|
282
|
+
ResultCode: int | str = Field(
|
|
283
283
|
default=0,
|
|
284
284
|
description="Result code (0=Success, other=Failure).",
|
|
285
285
|
)
|
|
@@ -108,7 +108,7 @@ class B2BExpressCheckoutCallback(BaseModel):
|
|
|
108
108
|
class B2BExpressCallbackResponse(BaseModel):
|
|
109
109
|
"""Response schema for B2B Express Checkout callback."""
|
|
110
110
|
|
|
111
|
-
ResultCode: int = Field(
|
|
111
|
+
ResultCode: int | str = Field(
|
|
112
112
|
default=0,
|
|
113
113
|
description="Result code (0=Success, other=Failure).",
|
|
114
114
|
)
|
|
@@ -43,5 +43,5 @@ class B2C(BaseModel):
|
|
|
43
43
|
"Authorization": f"Bearer {self.token_manager.get_token()}",
|
|
44
44
|
"Content-Type": "application/json",
|
|
45
45
|
}
|
|
46
|
-
response_data = self.http_client.post(url, json=
|
|
46
|
+
response_data = self.http_client.post(url, json=request.model_dump(by_alias=True), headers=headers)
|
|
47
47
|
return B2CResponse(**response_data)
|
|
@@ -167,7 +167,7 @@ class B2CResultMetadata(BaseModel):
|
|
|
167
167
|
"""Metadata for B2C result notification."""
|
|
168
168
|
|
|
169
169
|
ResultType: int = Field(..., description="Type of result (0=Success, 1=Failure).")
|
|
170
|
-
ResultCode: int = Field(..., description="Result code (0=Success).")
|
|
170
|
+
ResultCode: int | str = Field(..., description="Result code (0=Success).")
|
|
171
171
|
ResultDesc: str = Field(..., description="Result description.")
|
|
172
172
|
OriginatorConversationID: str = Field(
|
|
173
173
|
..., description="Originator conversation ID."
|
|
@@ -279,7 +279,7 @@ class B2CResultCallback(BaseModel):
|
|
|
279
279
|
class B2CResultCallbackResponse(BaseModel):
|
|
280
280
|
"""Schema for response to B2C result callback."""
|
|
281
281
|
|
|
282
|
-
ResultCode: int = Field(
|
|
282
|
+
ResultCode: int | str = Field(
|
|
283
283
|
default=0, description="Result code (0=Success, other=Failure)."
|
|
284
284
|
)
|
|
285
285
|
ResultDesc: str = Field(
|
|
@@ -320,7 +320,7 @@ class B2CTimeoutCallback(BaseModel):
|
|
|
320
320
|
class B2CTimeoutCallbackResponse(BaseModel):
|
|
321
321
|
"""Schema for response to B2C timeout callback."""
|
|
322
322
|
|
|
323
|
-
ResultCode: int = Field(
|
|
323
|
+
ResultCode: int | str = Field(
|
|
324
324
|
default=0,
|
|
325
325
|
description="Result code (0=Success, other=Failure).",
|
|
326
326
|
)
|
|
@@ -120,7 +120,7 @@ class B2CAccountTopUpCallbackResult(BaseModel):
|
|
|
120
120
|
ResultType: int = Field(
|
|
121
121
|
..., description="Status code for transaction sent to listener."
|
|
122
122
|
)
|
|
123
|
-
ResultCode: int = Field(
|
|
123
|
+
ResultCode: int | str = Field(
|
|
124
124
|
..., description="Transaction result status code. 0 means success."
|
|
125
125
|
)
|
|
126
126
|
ResultDesc: str = Field(
|
|
@@ -231,13 +231,13 @@ class B2CAccountTopUpCallback(BaseModel):
|
|
|
231
231
|
|
|
232
232
|
def is_successful(self) -> bool:
|
|
233
233
|
"""Check if the callback indicates a successful transaction."""
|
|
234
|
-
return self.Result.ResultCode == 0
|
|
234
|
+
return str(self.Result.ResultCode) == "0"
|
|
235
235
|
|
|
236
236
|
|
|
237
237
|
class B2CAccountTopUpCallbackResponse(BaseModel):
|
|
238
238
|
"""Response schema for B2C Account TopUp callback."""
|
|
239
239
|
|
|
240
|
-
ResultCode: int = Field(
|
|
240
|
+
ResultCode: int | str = Field(
|
|
241
241
|
default=0, description="Result code of the callback. 0 indicates success."
|
|
242
242
|
)
|
|
243
243
|
ResultDesc: str = Field(
|
|
@@ -297,7 +297,7 @@ class B2CAccountTopUpTimeoutCallback(BaseModel):
|
|
|
297
297
|
class B2CAccountTopUpTimeoutCallbackResponse(BaseModel):
|
|
298
298
|
"""Schema for response to B2C Account TopUp timeout callback."""
|
|
299
299
|
|
|
300
|
-
ResultCode: int = Field(
|
|
300
|
+
ResultCode: int | str = Field(
|
|
301
301
|
default=0,
|
|
302
302
|
description="Result code (0=Success, other=Failure).",
|
|
303
303
|
)
|
|
@@ -43,5 +43,5 @@ class BusinessBuyGoods(BaseModel):
|
|
|
43
43
|
"Authorization": f"Bearer {self.token_manager.get_token()}",
|
|
44
44
|
"Content-Type": "application/json",
|
|
45
45
|
}
|
|
46
|
-
response_data = self.http_client.post(url, json=
|
|
46
|
+
response_data = self.http_client.post(url, json=request.model_dump(by_alias=True), headers=headers)
|
|
47
47
|
return BusinessBuyGoodsResponse(**response_data)
|
|
@@ -240,7 +240,7 @@ class BusinessBuyGoodsResultCallback(BaseModel):
|
|
|
240
240
|
class BusinessBuyGoodsResultCallbackResponse(BaseModel):
|
|
241
241
|
"""Response schema for Business Buy Goods result callback."""
|
|
242
242
|
|
|
243
|
-
ResultCode: int = 0
|
|
243
|
+
ResultCode: int | str = 0
|
|
244
244
|
ResultDesc: str = "Callback received successfully."
|
|
245
245
|
|
|
246
246
|
model_config = ConfigDict(
|
|
@@ -289,7 +289,7 @@ class BusinessBuyGoodsTimeoutCallback(BaseModel):
|
|
|
289
289
|
class BusinessBuyGoodsTimeoutCallbackResponse(BaseModel):
|
|
290
290
|
"""Response schema for Business Buy Goods timeout callback."""
|
|
291
291
|
|
|
292
|
-
ResultCode: int = 0
|
|
292
|
+
ResultCode: int | str = 0
|
|
293
293
|
ResultDesc: str = "Timeout notification received successfully."
|
|
294
294
|
|
|
295
295
|
model_config = ConfigDict(
|
|
@@ -43,5 +43,5 @@ class BusinessPayBill(BaseModel):
|
|
|
43
43
|
"Authorization": f"Bearer {self.token_manager.get_token()}",
|
|
44
44
|
"Content-Type": "application/json",
|
|
45
45
|
}
|
|
46
|
-
response_data = self.http_client.post(url, json=
|
|
46
|
+
response_data = self.http_client.post(url, json=request.model_dump(by_alias=True), headers=headers)
|
|
47
47
|
return BusinessPayBillResponse(**response_data)
|
|
@@ -229,13 +229,13 @@ class BusinessPayBillResultCallback(BaseModel):
|
|
|
229
229
|
|
|
230
230
|
def is_successful(self) -> bool:
|
|
231
231
|
"""Check if the result indicates success."""
|
|
232
|
-
return self.Result.ResultCode == 0
|
|
232
|
+
return str(self.Result.ResultCode) == "0"
|
|
233
233
|
|
|
234
234
|
|
|
235
235
|
class BusinessPayBillResultCallbackResponse(BaseModel):
|
|
236
236
|
"""Response schema for Business PayBill result callback."""
|
|
237
237
|
|
|
238
|
-
ResultCode: int = 0
|
|
238
|
+
ResultCode: int | str = 0
|
|
239
239
|
ResultDesc: str = "Callback received successfully."
|
|
240
240
|
|
|
241
241
|
model_config = ConfigDict(
|
|
@@ -271,7 +271,7 @@ class BusinessPayBillTimeoutCallback(BaseModel):
|
|
|
271
271
|
class BusinessPayBillTimeoutCallbackResponse(BaseModel):
|
|
272
272
|
"""Response schema for Business PayBill timeout callback."""
|
|
273
273
|
|
|
274
|
-
ResultCode: int = 0
|
|
274
|
+
ResultCode: int | str = 0
|
|
275
275
|
ResultDesc: str = "Timeout notification received successfully."
|
|
276
276
|
|
|
277
277
|
model_config = ConfigDict(
|
|
@@ -41,7 +41,7 @@ class C2B(BaseModel):
|
|
|
41
41
|
"Authorization": f"Bearer {self.token_manager.get_token()}",
|
|
42
42
|
"Content-Type": "application/json",
|
|
43
43
|
}
|
|
44
|
-
response_data = self.http_client.post(url, json=
|
|
44
|
+
response_data = self.http_client.post(url, json=request.model_dump(by_alias=True), headers=headers)
|
|
45
45
|
|
|
46
46
|
# Safaricom API Bug: There is a typo in the response field name
|
|
47
47
|
# "OriginatorCoversationID" should be "OriginatorConversationID"
|
|
@@ -152,7 +152,7 @@ class C2BValidationRequest(BaseModel):
|
|
|
152
152
|
ThirdPartyTransID: Optional[str] = Field(
|
|
153
153
|
None, description="Partner transaction ID (optional)."
|
|
154
154
|
)
|
|
155
|
-
MSISDN: int = Field(..., description="Customer mobile number.")
|
|
155
|
+
MSISDN: int | str = Field(..., description="Customer mobile number.")
|
|
156
156
|
FirstName: Optional[str] = Field(None, description="Customer's first name.")
|
|
157
157
|
MiddleName: Optional[str] = Field(None, description="Customer's middle name.")
|
|
158
158
|
LastName: Optional[str] = Field(None, description="Customer's last name.")
|
|
@@ -226,7 +226,7 @@ class C2BValidationResponse(BaseModel):
|
|
|
226
226
|
class C2BConfirmationResponse(BaseModel):
|
|
227
227
|
"""Schema for confirmation acknowledgment from your ConfirmationURL."""
|
|
228
228
|
|
|
229
|
-
ResultCode: int = Field(0, description="Always 0 (success).")
|
|
229
|
+
ResultCode: int | str = Field(0, description="Always 0 (success).")
|
|
230
230
|
ResultDesc: str = Field("Success", description="Usually 'Success'.")
|
|
231
231
|
|
|
232
232
|
model_config = ConfigDict(
|
|
@@ -44,6 +44,6 @@ class DynamicQRCode(BaseModel):
|
|
|
44
44
|
"Content-Type": "application/json",
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
response_data = self.http_client.post(url, json=
|
|
47
|
+
response_data = self.http_client.post(url, json=request.model_dump(by_alias=True), headers=headers)
|
|
48
48
|
|
|
49
49
|
return DynamicQRGenerateResponse(**response_data)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""http_client.py: Defines an abstract base HTTP client class for making HTTP requests.
|
|
2
|
+
|
|
3
|
+
Provides a reusable interface for GET and POST requests.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HttpClient(ABC):
|
|
11
|
+
"""Abstract base HTTP client for making GET and POST requests."""
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def post(
|
|
15
|
+
self, url: str, json: Dict[str, Any], headers: Dict[str, str]
|
|
16
|
+
) -> Dict[str, Any]:
|
|
17
|
+
"""Sends a POST request."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def get(
|
|
22
|
+
self,
|
|
23
|
+
url: str,
|
|
24
|
+
params: Optional[Dict[str, Any]] = None,
|
|
25
|
+
headers: Optional[Dict[str, str]] = None,
|
|
26
|
+
) -> Dict[str, Any]:
|
|
27
|
+
"""Sends a GET request."""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
class AsyncHttpClient(ABC):
|
|
31
|
+
"""Abstract base HTTP client for making asynchronous GET and POST requests.
|
|
32
|
+
|
|
33
|
+
This is the base class for all asynchronous HTTP client implementations.
|
|
34
|
+
It provides the foundation for making asynchronous HTTP requests, which are
|
|
35
|
+
essential for interacting with the Mpesa API non-blockingly.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
async def post(
|
|
40
|
+
self, url: str, json: Dict[str, Any], headers: Dict[str, str]
|
|
41
|
+
) -> Dict[str, Any]:
|
|
42
|
+
"""Sends an asynchronous POST request."""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
async def get(
|
|
47
|
+
self,
|
|
48
|
+
url: str,
|
|
49
|
+
params: Optional[Dict[str, Any]] = None,
|
|
50
|
+
headers: Optional[Dict[str, str]] = None,
|
|
51
|
+
) -> Dict[str, Any]:
|
|
52
|
+
"""Sends an asynchronous GET request."""
|
|
53
|
+
pass
|