A reusable Django package for payment orchestration with clean architecture, idempotency, provider abstraction, and webhook handling.
- Provider abstraction (
paystack,stripe,monnify) through a shared interface. - Idempotent payment initialization flow to avoid duplicate charges.
- Service-layer orchestration (
PaymentService,WebhookService) for testable business logic. - Django REST API endpoints for charge, verify, and webhook processing.
- Extensible architecture for adding new providers without changing API/service boundaries.
payment_infra/
├── api/ # DRF serializers, views, and URL routes
├── application/ # Use-cases/services + provider/repository interfaces
├── domain/ # Core entities and domain models
├── infrastructure/
│ ├── idempotency/ # Idempotency persistence and locking
│ ├── providers/ # Gateway adapters (Paystack/Stripe/Monnify)
│ ├── repositories/ # Data access adapters
│ └── tasks/ # Async task hooks (Celery)
└── migrations/ # Django migrations
pip install payment_infragit clone https://github.com/0FFSIDE1/payment_infra.git
cd payment_infra
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtINSTALLED_APPS = [
# ...
"rest_framework",
"payment_infra",
]from django.urls import include, path
urlpatterns = [
# ...
path("payments/", include("payment_infra.api.urls")),
]Set DJANGO_PAYMENTS_PROVIDER to one of:
paystackstripemonnify
Provider-specific settings:
PAYSTACK_SECRET_KEYPAYSTACK_PUBLIC_KEYPAYSTACK_BASE_URL(optional)PAYSTACK_CALLBACK_URL
STRIPE_SECRET_KEYSTRIPE_PUBLIC_KEYSTRIPE_BASE_URL(optional, defaults tohttps://api.stripe.com/v1)STRIPE_WEBHOOK_SECRET
MONNIFY_API_KEYMONNIFY_SECRET_KEYMONNIFY_CONTRACT_CODEMONNIFY_PUBLIC_KEY(optional)MONNIFY_BASE_URL(optional)
Common runtime settings:
DJANGO_SECRET_KEYREDIS_URLCACHE_BACKEND
python manage.py migrateWith URLs mounted at /payments/:
POST /payments/charge/GET /payments/verify/<reference>/POST /payments/webhooks/
curl -X POST http://localhost:8000/payments/charge/ \
-H "Content-Type: application/json" \
-d '{
"email": "customer@example.com",
"amount": "1000.00",
"currency": "NGN",
"idempotency_key": "trx-20260411103000-abc123",
"callback_url": "https://example.com/payment/callback"
}'curl -X GET "http://localhost:8000/payments/verify/<reference>/"from decimal import Decimal
from payment_infra.infrastructure.providers.registry import get_payment_service
service = get_payment_service()
result = service.process_payment(
email="customer@example.com",
amount=Decimal("1000.00"),
currency="NGN",
idempotency_key="trx-20260411103000-abc123",
metadata={"callback_url": "https://example.com/payment/callback"},
)Run all tests:
pytestRun only non-integration tests:
pytest -m "not integration"- Always use HTTPS callback/webhook URLs in production.
- Keep provider secret keys in environment variables (never commit them).
- Verify webhook signatures before processing events.
- Use unique idempotency keys per client request to prevent duplicate processing.
- Create a feature branch.
- Add/modify tests for changes.
- Run
pytestlocally. - Open a pull request.
MIT — see LICENSE.