# Notch Pay Python SDK

[![PyPI version](https://img.shields.io/pypi/v/notchpay.svg)](https://pypi.org/project/notchpay/)
[![Python versions](https://img.shields.io/pypi/pyversions/notchpay.svg)](https://pypi.org/project/notchpay/)
[![License](https://img.shields.io/pypi/l/notchpay.svg)](https://github.com/Wilfried-Tech/notchpay-python/blob/master/LICENSE)

> Unofficial Python SDK for integrating the Notch Pay API

The Notch Pay Python SDK provides a convenient way to integrate Notch Pay into your Python applications, including frameworks like Django, Flask, and FastAPI. This package provides a simple, typed interface for interacting with all Notch Pay features.

## Features

- ✅ **Full API Coverage** - Complete implementation of all Notch Pay API endpoints
- 🔒 **Type Safety** - Fully typed with Pydantic models for request/response validation
- ⚡ **Async Support** - Built-in async/await support for asynchronous frameworks
- 🎯 **Framework Integration** - Ready-to-use examples for Django, Flask, and FastAPI
- 📝 **Well Documented** - Comprehensive documentation with examples
- 🧪 **Fully Tested** - Extensive test coverage with pytest

## Installation

Install the package using your preferred package manager:

### Using pip

```bash
pip install notchpay
```

### Using poetry

```bash
poetry add notchpay
```

### Using pipenv

```bash
pipenv install notchpay
```

### Using uv

```bash
uv add notchpay
```

## Quick Start

### Initialize the SDK

```python
from notchpay import NotchPay

# Initialize with your API key
notchpay = NotchPay('YOUR_PUBLIC_KEY')

# For endpoints requiring advanced authentication
notchpay.set_grant_key('YOUR_PRIVATE_KEY')
```

### Create a Payment

```python
try:
    payment = notchpay.payments.create({
        'amount': 5000,
        'currency': 'XAF',
        'customer': {
            'email': 'customer@example.com',
            'name': 'John Doe'
        },
        'reference': 'order_123',
        'callback': 'https://example.com/callback',
        'description': 'Payment for Order #123'
    })
    
    # Redirect to the payment page
    print(f"Redirect to: {payment.authorization_url}")
except Exception as e:
    print(f"Error: {str(e)}")
```

### Retrieve a Payment

```python
try:
    reference = 'order_123'
    payment = notchpay.payments.retrieve(reference)
    
    if payment.transaction.status == 'complete':
        # Payment is complete, fulfill the order
        print('Payment complete!')
    else:
        # Payment is not complete
        print(f"Payment status: {payment.transaction.status}")
except Exception as e:
    print(f"Error: {str(e)}")
```

## API Reference

### Payments

Manage payment transactions.

```python
# Create a payment
payment = notchpay.payments.create(data)

# Retrieve a payment by reference
payment = notchpay.payments.retrieve(reference)

# List all payments
payments = notchpay.payments.list(params)

# Cancel a payment
notchpay.payments.cancel(reference)

# Process a payment
notchpay.payments.process(reference, data)

# Direct charge
notchpay.payments.direct_charge(data)
```

**References:**

- [Initialize a Payment](https://developer.notchpay.co/api-reference/initialize-a-payment)
- [Retrieve a Payment](https://developer.notchpay.co/api-reference/retrieve-a-payment)
- [List All Payments](https://developer.notchpay.co/api-reference/list-all-payments)
- [Cancel a Payment](https://developer.notchpay.co/api-reference/cancel-a-payment)

### Transfers

Send money to beneficiaries.

```python
# Create a transfer
transfer = notchpay.transfers.create(data)

# Retrieve a transfer
transfer = notchpay.transfers.retrieve(reference)

# List all transfers
transfers = notchpay.transfers.list(params)

# Cancel a transfer
notchpay.transfers.cancel(reference)
```

**References:**

- [Create a Transfer](https://developer.notchpay.co/api-reference/initiate-a-transfer)
- [Retrieve a Transfer](https://developer.notchpay.co/api-reference/retrieve-a-transfer)
- [List All Transfers](https://developer.notchpay.co/api-reference/list-all-transfers)

### Customers

Manage customer profiles.

```python
# Create a customer
customer = notchpay.customers.create(data)

# Retrieve a customer
customer = notchpay.customers.retrieve(customer_id)

# Update a customer
customer = notchpay.customers.update(customer_id, data)

# List all customers
customers = notchpay.customers.list(params)

# Delete a customer
notchpay.customers.delete(customer_id)

# Get customer payments
payments = notchpay.customers.payments(customer_id)

# Get customer payment methods
methods = notchpay.customers.payment_methods(customer_id)
```

**References:**

- [Create a Customer](https://developer.notchpay.co/api-reference/create-a-customer)
- [Retrieve a Customer](https://developer.notchpay.co/api-reference/retrieve-a-customer)
- [List All Customers](https://developer.notchpay.co/api-reference/list-all-customers)

### Beneficiaries

Manage transfer beneficiaries.

```python
# Create a beneficiary
beneficiary = notchpay.beneficiaries.create(data)

# Retrieve a beneficiary
beneficiary = notchpay.beneficiaries.retrieve(beneficiary_id)

# Update a beneficiary
beneficiary = notchpay.beneficiaries.update(beneficiary_id, data)

# List all beneficiaries
beneficiaries = notchpay.beneficiaries.list(params)

# Delete a beneficiary
notchpay.beneficiaries.delete(beneficiary_id)
```

**References:**

- [Create a Beneficiary](https://developer.notchpay.co/api-reference/create-a-beneficiary)
- [Retrieve a Beneficiary](https://developer.notchpay.co/api-reference/retrieve-a-beneficiary)
- [List All Beneficiaries](https://developer.notchpay.co/api-reference/list-all-beneficiaries)

### Balance

Check account balance.

```python
# Get balance
balance = notchpay.balance.retrieve()

# Access available balance
for currency, amount in balance.balance.available.items():
    print(f"{currency}: {amount}")
```

**References:**

- [Retrieve Balance](https://developer.notchpay.co/api-reference/retrieve-the-balance)

### Webhooks

Manage webhook endpoints.

```python
# Create a webhook
webhook = notchpay.webhooks.create(data)

# Retrieve a webhook
webhook = notchpay.webhooks.retrieve(webhook_id)

# Update a webhook
webhook = notchpay.webhooks.update(webhook_id, data)

# List all webhooks
webhooks = notchpay.webhooks.list(params)

# Delete a webhook
notchpay.webhooks.delete(webhook_id)
```

**References:**

- [Create a Webhook](https://developer.notchpay.co/api-reference/create-a-webhook-endpoint)
- [Retrieve a Webhook](https://developer.notchpay.co/api-reference/retrieve-a-webhook-endpoint)
- [List All Webhooks](https://developer.notchpay.co/api-reference/list-all-webhook-endpoints)

### Channels

Get available payment channels.

```python
# List available channels
channels = notchpay.channels.list(country="CM", amount=1000, currency="XAF")

for channel in channels.items:
    print(channel.name)
```

**References:**

- [List Payment Channels](https://developer.notchpay.co/api-reference/list-all-payment-channels)

## Async Support

The SDK provides full async support for use with async frameworks:

```python
from notchpay import AsyncNotchPay

# Initialize the async SDK
notchpay = AsyncNotchPay('YOUR_PUBLIC_KEY')

async def create_payment_async():
    try:
        payment = await notchpay.payments.create({
            'amount': 5000,
            'currency': 'XAF',
            'customer': {
                'email': 'customer@example.com'
            },
            'reference': 'order_123'
        })
        
        return payment
    except Exception as e:
        print(f"Error: {str(e)}")
        return None
```

## Framework Integration

### Django

```python
from django.conf import settings
from notchpay import NotchPay

# settings.py
NOTCHPAY_API_KEY = 'YOUR_PUBLIC_KEY'
NOTCHPAY_GRANT_KEY = 'YOUR_PRIVATE_KEY'

# views.py
notchpay = NotchPay(settings.NOTCHPAY_API_KEY)

def create_payment(request):
    payment = notchpay.payments.create({
        'amount': 5000,
        'currency': 'XAF',
        'customer': {
            'email': request.user.email,
            'name': request.user.get_full_name()
        },
        'reference': f"order_{order_id}",
        'callback': request.build_absolute_uri('/payment/callback/'),
    })
    
    return redirect(payment.authorization_url)
```

### Flask

```python
from flask import Flask
from notchpay import NotchPay

app = Flask(__name__)
notchpay = NotchPay('YOUR_PUBLIC_KEY')

@app.route('/payment/create', methods=['POST'])
def create_payment():
    payment = notchpay.payments.create({
        'amount': 5000,
        'currency': 'XAF',
        'customer': {
            'email': request.form.get('email')
        },
        'reference': f"order_{int(time.time())}",
    })
    
    return redirect(payment.authorization_url)
```

### FastAPI

```python
from fastapi import FastAPI
from notchpay import AsyncNotchPay

app = FastAPI()
notchpay = AsyncNotchPay('YOUR_PUBLIC_KEY')

@app.post("/payment/create")
async def create_payment(email: str, amount: int):
    payment = await notchpay.payments.create({
        'amount': amount,
        'currency': 'XAF',
        'customer': {
            'email': email
        },
        'reference': f"order_{int(time.time())}",
    })
    
    return {"authorization_url": payment.authorization_url}
```

For complete integration examples, see the [official documentation](https://developer.notchpay.co/sdks/python).

## Error Handling

The SDK raises specific exceptions that you can catch and handle:

```python
from notchpay.exceptions import (
    NotchPayError,
    ValidationError,
    AuthenticationError,
    APIError
)

try:
    payment = notchpay.payments.create(data)
except ValidationError as e:
    # Handle validation errors
    errors = e.errors
    for field, messages in errors.items():
        print(f"{field}: {', '.join(messages)}")
except AuthenticationError as e:
    # Handle authentication errors
    print(f"Authentication error: {str(e)}")
except APIError as e:
    # Handle API errors
    print(f"API error: {str(e)}")
    print(f"Error code: {e.code}")
except NotchPayError as e:
    # Handle other Notch Pay errors
    print(f"Notch Pay error: {str(e)}")
```

## Webhook Verification

Verify webhook signatures to ensure requests are from Notch Pay:

```python
from notchpay import NotchPay

# In your webhook handler
payload = request.body.decode('utf-8')
signature = request.headers.get('X-Notchpay-Signature', '')

if NotchPay.verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
    # Process the webhook
    event = json.loads(payload)
else:
    # Invalid signature
    return Response(status=401)
```

## Development

### Setup

```bash
# Clone the repository
git clone https://github.com/Wilfried-Tech/notchpay-python.git
cd notchpay-python

# Install dependencies using uv
uv sync

# Or using pip
pip install -e ".[dev]"
```

### Running Tests

```bash
# Run tests with coverage
uv run pytest --cov=src/notchpay --cov-report=html

# Run tests with verbose output
uv run pytest -v

# Run specific test file
uv run pytest tests/test_payments.py
```

### Type Checking

```bash
uv run mypy src/notchpay
```

## Requirements

- Python 3.9 or higher
- httpx >= 0.28.1
- pydantic >= 2.12.5

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Support

- 📖 [Official Documentation](https://developer.notchpay.co/sdks/python)
- 🐛 [Issue Tracker](https://github.com/Wilfried-Tech/notchpay-python/issues)
- 💬 [Discussions](https://github.com/Wilfried-Tech/notchpay-python/discussions)

## Disclaimer

This is an unofficial SDK and is not affiliated with or endorsed by Notch Pay. Use at your own risk.

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for a detailed history of changes.

---

Made with ❤️ by [Wilfried-Tech](https://github.com/Wilfried-Tech)
