Metadata-Version: 2.4
Name: mpesakit
Version: 2.2.0
Summary: A Python SDK for integrating with M-Pesa APIs
Project-URL: Homepage, https://github.com/Byte-Barn/mpesakit
Project-URL: Issues, https://github.com/Byte-Barn/mpesakit/issues
Project-URL: Repository, https://github.com/Byte-Barn/mpesakit
Author-email: John Kagunda <johnmkagunda@gmail.com>
Maintainer-email: John Kagunda <johnmkagunda@gmail.com>
License: APACHE-2.0
License-File: AUTHORS.md
License-File: LICENSE
Keywords: africa,api,django,fastapi,flask,mobile money,mpesa,payment,payments,rest,sdk
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: cryptography>=41.0.7
Requires-Dist: httpx<1.0.0,>=0.27.0
Requires-Dist: pydantic<3.0.0,>=2.10.6
Requires-Dist: pydantic[email]<3.0.0,>=2.10.6
Requires-Dist: requests<3.0.0,>=2.32.5
Requires-Dist: tenacity<10.0.0,>=9.1.2
Requires-Dist: typing-extensions<5.0.0,>=4.12.2
Provides-Extra: dev
Requires-Dist: bandit; extra == 'dev'
Requires-Dist: build; extra == 'dev'
Requires-Dist: coverage; extra == 'dev'
Requires-Dist: hatch; extra == 'dev'
Requires-Dist: hatch-vcs; extra == 'dev'
Requires-Dist: hatchling; extra == 'dev'
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-asyncio<1.0.0,>=0.23.6; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Requires-Dist: types-requests; extra == 'dev'
Provides-Extra: test
Requires-Dist: bandit; extra == 'test'
Requires-Dist: mypy; extra == 'test'
Requires-Dist: pyngrok; extra == 'test'
Requires-Dist: pytest; extra == 'test'
Requires-Dist: pytest-asyncio<1.0.0,>=0.23.6; extra == 'test'
Requires-Dist: pytest-cov; extra == 'test'
Requires-Dist: types-requests; extra == 'test'
Description-Content-Type: text/markdown

# mpesakit

> ⚡ Effortless M-Pesa integration using Safaricom's Daraja API — built for developers, by developers.

[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0)
[![PyPI version](https://img.shields.io/pypi/v/mpesakit.svg)](https://pypi.org/project/mpesakit)
[![Downloads](https://pepy.tech/badge/mpesakit)](https://pepy.tech/project/mpesakit)
[![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](https://github.com/Byte-Barn/mpesakit/actions)

---

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.

**`mpesakit`** handles all of that. Add your credentials, call a method, move on.

---

## Installation

```bash
pip install mpesakit
```

---

## Quick Start

### 1. Set your credentials

```bash
export MPESA_CONSUMER_KEY="your_consumer_key"
export MPESA_CONSUMER_SECRET="your_consumer_secret"
export MPESA_SHORTCODE="your_shortcode"
export MPESA_PASSKEY="your_lipa_na_mpesa_passkey"
export MPESA_PHONE_NUMBER="254712345678"
```

### 2. Trigger an STK Push

```python
import os
from dotenv import load_dotenv
from mpesakit import MpesaClient
from mpesakit.mpesa_express import TransactionType

load_dotenv()

client = MpesaClient(
    consumer_key=os.getenv("MPESA_CONSUMER_KEY"),
    consumer_secret=os.getenv("MPESA_CONSUMER_SECRET"),
    environment="sandbox",  # Switch to "production" when ready
)

response = client.stk_push(
    business_short_code=int(os.getenv("MPESA_SHORTCODE")),
    passkey=os.getenv("MPESA_PASSKEY"),
    transaction_type=TransactionType.CUSTOMER_PAYBILL_ONLINE,
    amount=250,
    party_a=os.getenv("MPESA_PHONE_NUMBER"),
    party_b=os.getenv("MPESA_SHORTCODE"),
    phone_number=os.getenv("MPESA_PHONE_NUMBER"),
    callback_url="https://yourdomain.com/mpesa/callback",
    account_reference="Order-001",
    transaction_desc="Payment for order",
)

if response.is_successful():
    print("Request accepted:", response.CheckoutRequestID)
else:
    print("Error:", response.error_message())
```

### 3. Handle the payment callback

The client exposes `process_*` methods that validate and deserialize incoming Safaricom payloads into typed Pydantic objects — no manual dict parsing required.

```python
# Example: FastAPI callback handler
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.post("/mpesa/callback")
async def mpesa_callback(request: Request):
    payload = await request.json()

    # Validates the payload and returns a typed StkPushSimulateCallback object
    callback = client.process_stk_callback(payload)

    if callback.is_successful()
        metadata = callback.Body.stkCallback.CallbackMetadata
        print(f"Payment confirmed — Receipt: {metadata}")
    else:
        print(f"Payment failed: {callback.Body.stkCallback.ResultDesc}")

    return JSONResponse({"ResultCode": 0, "ResultDesc": "Accepted"})
```

All `process_*` methods follow the same pattern — pass in the raw JSON payload, get back a validated object:

| Method | Returns |
|--------|---------|
| `client.process_stk_callback(payload)` | `StkPushSimulateCallback` |
| `client.process_stk_query_callback(payload)` | `StkPushQueryResponse` |
| `client.process_b2c_callback(payload)` | `B2CResultCallback` |
| `client.process_account_balance_callback(payload)` | `AccountBalanceResultCallback` |
| `client.process_account_balance_timeout(payload)` | `AccountBalanceTimeoutCallback` |
| `client.process_transcations_callback(payload)` | `TransactionStatusResultCallback` |
| `client.process_reversal_callback(payload)` | `ReversalResultCallback` |
| `client.process_tax_remittance_callback(payload)` | `TaxRemittanceResultCallback` |
| `client.process_dynamic_qr_code_callback(payload)` | `DynamicQRGenerateResponse` |
| `client.process_b2b_callback(payload)` | `B2BExpressCheckoutCallback` |
| `client.process_bill_manager_callback(payload)` | `BillManagerPaymentNotificationRequest` |
| `client.process_ratiba_service_callback(payload)` | `StandingOrderCallback` |

### 4. Error handling

```python
from mpesakit.errors import MpesaApiException

try:
    response = client.stk_push(...)
except MpesaApiException as e:
    err = e.error
    print(f"Code: {err.error_code}")       # e.g. AUTH_INVALID_CREDENTIALS
    print(f"Message: {err.error_message}") # Human-readable description
    print(f"HTTP status: {err.status_code}")
    print(f"Request ID: {err.request_id}")
except Exception as exc:
    print(f"Unexpected error: {exc}")
```

---

## More Examples

### B2C — Send money to a customer

```python
from mpesakit.b2c import B2CCommandIDType

response = client.b2c.send_payment(
    originator_conversation_id="ocid-1234-5678",
    initiator_name="your_initiator_name",
    security_credential="your_encrypted_security_credential",
    command_id=B2CCommandIDType.BusinessPayment,
    amount=1500,
    party_a="600999",           # Your bulk disbursement shortcode
    party_b="254712345678",     # Recipient phone number (normalized by SDK)
    remarks="Refund for order 042",
    queue_timeout_url="https://yourdomain.com/mpesa/timeout",
    result_url="https://yourdomain.com/mpesa/result",
)

if response.is_successful():
    print("Payout sent:", response.ResponseDescription)
```

> **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.

### STK Query — Check a push status

```python
response = client.stk_query(
    business_short_code=int(os.getenv("MPESA_SHORTCODE")),
    passkey=os.getenv("MPESA_PASSKEY"),
    checkout_request_id="ws_CO_191220191020363925",
)
```

### Switching to production

```python
client = MpesaClient(
    consumer_key="...",
    consumer_secret="...",
    environment="production",  # That's all it takes
)
```

---

## Supported APIs

| API | Status | Description |
|-----|--------|-------------|
| **STK Push** | ✅ Ready | Prompt a customer to enter their M-Pesa PIN to pay |
| **STK Query** | ✅ Ready | Check the status of an STK Push request |
| **C2B Payments** | ✅ Ready | Receive payments from customers via paybill or till |
| **B2C Payments** | ✅ Ready | Send money to customers or staff |
| **B2C Account Top-up** | ✅ Ready | Top up B2C utility accounts |
| **Business Paybill** | ✅ Ready | Business-to-business paybill transfers |
| **Business BuyGoods** | ✅ Ready | Business-to-business till transfers |
| **Token Management** | ✅ Ready | Automatic OAuth2 token handling — no manual refresh needed |
| **Account Balance** | ✅ Ready | Query your M-Pesa account balance |
| **Transaction Status** | ✅ Ready | Look up the status of any past transaction |
| **Transaction Reversal** | ✅ Ready | Reverse erroneous transactions |
| **Dynamic QR** | ✅ Ready | Generate QR codes for M-Pesa payments |
| **Tax Remittance** | ✅ Ready | Submit tax remittances via M-Pesa |

---

## Security Best Practices

- **Never commit credentials** to version control — use environment variables or a secrets manager
- **Validate callbacks** using `is_mpesa_ip_allowed` to restrict requests to known Safaricom IP ranges
- **Use HTTPS** for all callback URLs — Safaricom will not deliver to plain HTTP in production
- **Log transaction IDs** (`OriginatorConversationID`, `ConversationID`) for reconciliation and dispute resolution
- **Persist callback payloads** before returning an acknowledgement, to protect against processing failures

---

## Full Documentation

API reference, webhook guides, and production checklist: **[mpesakit.dev](https://mpesakit.dev)**

---

## Contributing

```bash
git clone https://github.com/Byte-Barn/mpesakit.git
cd mpesakit

python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

pip install -e ".[dev]"
pytest tests/unit
```

Ways to contribute:
- **Report bugs** — [GitHub Issues](https://github.com/Byte-Barn/mpesakit/issues)
- **Suggest features** — [GitHub Discussions](https://github.com/Byte-Barn/mpesakit/discussions)
- **Submit a PR** — please include tests and update docs for any API changes
- **Join the community** — [Discord](https://discord.gg/hNxTew523E)

Please follow PEP 8 and include type hints in new code.

---

## Support

- 📖 Docs: [mpesakit.dev](https://mpesakit.dev)
- 🐛 Issues: [github.com/Byte-Barn/mpesakit/issues](https://github.com/Byte-Barn/mpesakit/issues)
- 💬 Discussions: [github.com/Byte-Barn/mpesakit/discussions](https://github.com/Byte-Barn/mpesakit/discussions)
- 📧 Email: johnmkagunda@gmail.com

---

## License

[Apache 2.0](LICENSE) — free for commercial and private use.

---

<div align="center">

**Made with ❤️ for the Kenyan developer community**

[⭐ 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)

*Built on the shoulders of [`Arlus/mpesa-py`](https://github.com/Arlus/mpesa-py)*

</div>
