paystack-django 1.2.0__tar.gz → 2.0.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.
- paystack_django-2.0.0/CHANGELOG.md +72 -0
- paystack_django-2.0.0/PKG-INFO +598 -0
- paystack_django-2.0.0/README.md +539 -0
- paystack_django-2.0.0/djpaystack/__init__.py +30 -0
- paystack_django-2.0.0/djpaystack/admin.py +77 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/api/__init__.py +47 -42
- paystack_django-2.0.0/djpaystack/api/apple_pay.py +24 -0
- paystack_django-2.0.0/djpaystack/api/base.py +120 -0
- paystack_django-2.0.0/djpaystack/api/bulk_charges.py +50 -0
- paystack_django-2.0.0/djpaystack/api/charge.py +75 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/api/customers.py +118 -59
- paystack_django-2.0.0/djpaystack/api/dedicated_accounts.py +113 -0
- paystack_django-2.0.0/djpaystack/api/direct_debit.py +39 -0
- paystack_django-2.0.0/djpaystack/api/disputes.py +104 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/api/integration.py +5 -4
- paystack_django-2.0.0/djpaystack/api/miscellaneous.py +45 -0
- paystack_django-2.0.0/djpaystack/api/order.py +75 -0
- paystack_django-2.0.0/djpaystack/api/pages.py +67 -0
- paystack_django-2.0.0/djpaystack/api/payment_requests.py +112 -0
- paystack_django-2.0.0/djpaystack/api/plans.py +72 -0
- paystack_django-2.0.0/djpaystack/api/products.py +67 -0
- paystack_django-2.0.0/djpaystack/api/refunds.py +56 -0
- paystack_django-2.0.0/djpaystack/api/settlements.py +35 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/api/splits.py +29 -18
- paystack_django-2.0.0/djpaystack/api/storefront.py +80 -0
- paystack_django-2.0.0/djpaystack/api/subaccounts.py +79 -0
- paystack_django-2.0.0/djpaystack/api/subscriptions.py +53 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/api/terminal.py +17 -14
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/api/transactions.py +48 -53
- paystack_django-2.0.0/djpaystack/api/transfer_control.py +42 -0
- paystack_django-2.0.0/djpaystack/api/transfer_recipients.py +62 -0
- paystack_django-2.0.0/djpaystack/api/transfers.py +81 -0
- paystack_django-2.0.0/djpaystack/api/verification.py +38 -0
- paystack_django-2.0.0/djpaystack/api/virtual_terminal.py +81 -0
- paystack_django-2.0.0/djpaystack/apps.py +14 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/client.py +74 -60
- paystack_django-2.0.0/djpaystack/decorators.py +34 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/dev/__init__.py +4 -4
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/dev/ngrok_tunnel.py +18 -26
- paystack_django-2.0.0/djpaystack/dev/webhook_tester.py +179 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/exceptions.py +5 -0
- paystack_django-2.0.0/djpaystack/management/commands/cleanup_paystack_logs.py +88 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/management/commands/list_webhook_events.py +27 -24
- paystack_django-2.0.0/djpaystack/management/commands/start_webhook_tunnel.py +86 -0
- paystack_django-2.0.0/djpaystack/management/commands/sync_paystack_data.py +85 -0
- paystack_django-2.0.0/djpaystack/management/commands/test_webhook.py +104 -0
- paystack_django-2.0.0/djpaystack/management/commands/verify_paystack_config.py +32 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/middleware.py +5 -6
- paystack_django-2.0.0/djpaystack/migrations/0002_alter_paystacktransfer_status.py +29 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/models.py +46 -47
- paystack_django-2.0.0/djpaystack/settings.py +91 -0
- paystack_django-2.0.0/djpaystack/signals.py +20 -0
- paystack_django-2.0.0/djpaystack/tests/conftest.py +65 -0
- paystack_django-2.0.0/djpaystack/tests/settings.py +25 -0
- paystack_django-2.0.0/djpaystack/tests/test_api_compliance.py +192 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/tests/test_client.py +39 -42
- paystack_django-2.0.0/djpaystack/tests/test_customers.py +55 -0
- paystack_django-2.0.0/djpaystack/tests/test_models.py +45 -0
- paystack_django-2.0.0/djpaystack/tests/test_new_endpoints.py +324 -0
- paystack_django-2.0.0/djpaystack/tests/test_production_hardening.py +179 -0
- paystack_django-2.0.0/djpaystack/tests/test_security_remediation.py +122 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/tests/test_transactions.py +40 -46
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/tests/test_utils.py +12 -12
- paystack_django-2.0.0/djpaystack/tests/test_webhooks.py +135 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/utils.py +12 -17
- paystack_django-2.0.0/djpaystack/views.py +1 -0
- paystack_django-2.0.0/djpaystack/webhooks/events.py +135 -0
- paystack_django-2.0.0/djpaystack/webhooks/handlers.py +438 -0
- paystack_django-2.0.0/djpaystack/webhooks/urls.py +9 -0
- paystack_django-2.0.0/djpaystack/webhooks/views.py +116 -0
- paystack_django-2.0.0/paystack_django.egg-info/PKG-INFO +598 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/paystack_django.egg-info/SOURCES.txt +7 -11
- {paystack_django-1.2.0 → paystack_django-2.0.0}/paystack_django.egg-info/requires.txt +1 -2
- {paystack_django-1.2.0 → paystack_django-2.0.0}/pyproject.toml +9 -10
- paystack_django-1.2.0/CHANGELOG.md +0 -155
- paystack_django-1.2.0/PKG-INFO +0 -409
- paystack_django-1.2.0/README.md +0 -344
- paystack_django-1.2.0/djpaystack/__init__.py +0 -26
- paystack_django-1.2.0/djpaystack/admin.py +0 -69
- paystack_django-1.2.0/djpaystack/api/apple_pay.py +0 -22
- paystack_django-1.2.0/djpaystack/api/base.py +0 -97
- paystack_django-1.2.0/djpaystack/api/bulk_charges.py +0 -39
- paystack_django-1.2.0/djpaystack/api/charge.py +0 -96
- paystack_django-1.2.0/djpaystack/api/dedicated_accounts.py +0 -157
- paystack_django-1.2.0/djpaystack/api/direct_debit.py +0 -104
- paystack_django-1.2.0/djpaystack/api/disputes.py +0 -64
- paystack_django-1.2.0/djpaystack/api/miscellaneous.py +0 -34
- paystack_django-1.2.0/djpaystack/api/pages.py +0 -42
- paystack_django-1.2.0/djpaystack/api/payment_requests.py +0 -68
- paystack_django-1.2.0/djpaystack/api/plans.py +0 -42
- paystack_django-1.2.0/djpaystack/api/products.py +0 -35
- paystack_django-1.2.0/djpaystack/api/refunds.py +0 -82
- paystack_django-1.2.0/djpaystack/api/settlements.py +0 -20
- paystack_django-1.2.0/djpaystack/api/subaccounts.py +0 -46
- paystack_django-1.2.0/djpaystack/api/subscriptions.py +0 -42
- paystack_django-1.2.0/djpaystack/api/transfer_control.py +0 -32
- paystack_django-1.2.0/djpaystack/api/transfer_recipients.py +0 -41
- paystack_django-1.2.0/djpaystack/api/transfers.py +0 -42
- paystack_django-1.2.0/djpaystack/api/verification.py +0 -26
- paystack_django-1.2.0/djpaystack/api/virtual_terminal.py +0 -11
- paystack_django-1.2.0/djpaystack/apps.py +0 -39
- paystack_django-1.2.0/djpaystack/decorators.py +0 -32
- paystack_django-1.2.0/djpaystack/dev/webhook_tester.py +0 -193
- paystack_django-1.2.0/djpaystack/management/commands/cleanup_paystack_logs.py +0 -97
- paystack_django-1.2.0/djpaystack/management/commands/paystack_listen.py +0 -219
- paystack_django-1.2.0/djpaystack/management/commands/paystack_webhook_event.py +0 -422
- paystack_django-1.2.0/djpaystack/management/commands/start_webhook_tunnel.py +0 -95
- paystack_django-1.2.0/djpaystack/management/commands/sync_paystack_data.py +0 -80
- paystack_django-1.2.0/djpaystack/management/commands/test_webhook.py +0 -106
- paystack_django-1.2.0/djpaystack/management/commands/verify_paystack_config.py +0 -35
- paystack_django-1.2.0/djpaystack/migrations/0002_remove_redundant_indexes.py +0 -21
- paystack_django-1.2.0/djpaystack/settings.py +0 -70
- paystack_django-1.2.0/djpaystack/signals.py +0 -43
- paystack_django-1.2.0/djpaystack/tests/conftest.py +0 -86
- paystack_django-1.2.0/djpaystack/tests/settings.py +0 -45
- paystack_django-1.2.0/djpaystack/tests/test_admin.py +0 -85
- paystack_django-1.2.0/djpaystack/tests/test_customers.py +0 -59
- paystack_django-1.2.0/djpaystack/tests/test_decorators.py +0 -81
- paystack_django-1.2.0/djpaystack/tests/test_middleware.py +0 -40
- paystack_django-1.2.0/djpaystack/tests/test_models.py +0 -49
- paystack_django-1.2.0/djpaystack/tests/test_settings.py +0 -94
- paystack_django-1.2.0/djpaystack/tests/test_views.py +0 -93
- paystack_django-1.2.0/djpaystack/tests/test_webhook_handlers.py +0 -304
- paystack_django-1.2.0/djpaystack/tests/test_webhook_security.py +0 -72
- paystack_django-1.2.0/djpaystack/tests/test_webhooks.py +0 -174
- paystack_django-1.2.0/djpaystack/views.py +0 -57
- paystack_django-1.2.0/djpaystack/webhooks/events.py +0 -126
- paystack_django-1.2.0/djpaystack/webhooks/handlers.py +0 -642
- paystack_django-1.2.0/djpaystack/webhooks/urls.py +0 -8
- paystack_django-1.2.0/djpaystack/webhooks/views.py +0 -104
- paystack_django-1.2.0/paystack_django.egg-info/PKG-INFO +0 -409
- paystack_django-1.2.0/setup.py +0 -72
- {paystack_django-1.2.0 → paystack_django-2.0.0}/CONTRIBUTING.md +0 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/LICENSE +0 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/MANIFEST.in +0 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/management/__init__.py +0 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/management/commands/__init__.py +0 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/migrations/0001_initial.py +0 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/migrations/__init__.py +0 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/py.typed +0 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/tests/__init__.py +0 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/djpaystack/webhooks/__init__.py +0 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/paystack_django.egg-info/dependency_links.txt +0 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/paystack_django.egg-info/top_level.txt +0 -0
- {paystack_django-1.2.0 → paystack_django-2.0.0}/setup.cfg +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.2.0] - 2026-06-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Clients for the full Paystack API surface, including **Virtual Terminal**,
|
|
13
|
+
**Direct Debit**, **Orders**, and **Storefronts**.
|
|
14
|
+
- Customer authorization and direct-debit onboarding: `initialize_authorization`,
|
|
15
|
+
`verify_authorization`, `initialize_direct_debit`,
|
|
16
|
+
`directdebit_activation_charge`, and `fetch_mandate_authorizations`.
|
|
17
|
+
- Additional endpoints: product `delete`, refund `retry_with_customer_details`,
|
|
18
|
+
transfer `export`, dedicated-account `assign`, and balance-ledger pagination.
|
|
19
|
+
- Lazy `iter_all()` iterators on `transactions` and `customers` to stream every
|
|
20
|
+
record across pages with bounded memory.
|
|
21
|
+
- `WEBHOOK_SIGNATURE_REQUIRED` setting (defaults to `True`).
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **`WEBHOOK_SECRET` now defaults to `SECRET_KEY`.** Paystack signs webhooks with
|
|
26
|
+
the account secret key, so `WEBHOOK_SECRET` is an optional override.
|
|
27
|
+
- Added a `User-Agent: paystack-django/<version>` header to all requests.
|
|
28
|
+
- HTTP 401/403 responses now raise `PaystackAuthenticationError`.
|
|
29
|
+
- Webhook datetime fields (`paid_at`, `next_payment_date`, `transferred_at`) are
|
|
30
|
+
parsed into real `datetime` objects.
|
|
31
|
+
- `sync_paystack_data` honours `--days` and iterates lazily.
|
|
32
|
+
- Consolidated build configuration onto `pyproject.toml`; removed `setup.py` and
|
|
33
|
+
`setup.cfg`. Removed the unused `python-decouple` dependency and raised the
|
|
34
|
+
`requests` floor to `>=2.32.0`.
|
|
35
|
+
- The package is fully formatted with black and isort, and CI now enforces
|
|
36
|
+
linting, formatting, typing, coverage, and dependency/security scanning.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- Webhook signature verification now **fails closed** when no signing key can be
|
|
41
|
+
resolved (previously it accepted unverified webhooks).
|
|
42
|
+
- Non-idempotent requests (`POST`/`PUT`) are no longer automatically retried,
|
|
43
|
+
preventing duplicate charges/transfers on transient errors.
|
|
44
|
+
- Webhook deduplication is now race-safe and database-backed, so concurrent or
|
|
45
|
+
redelivered webhooks are processed exactly once across multiple workers.
|
|
46
|
+
- List endpoints send the correct `from`/`to` date filters (were ignored).
|
|
47
|
+
- Dispute webhook events use their correct `charge.dispute.*` names, so dispute
|
|
48
|
+
handlers and signals fire.
|
|
49
|
+
- Bulk-charge `pause()`/`resume()` use the correct request method and path.
|
|
50
|
+
- `charge.submit_address()` sends `zip_code` (was `zipcode`).
|
|
51
|
+
- `transfers.list()` filters by `recipient` (with `customer` kept as a
|
|
52
|
+
deprecated alias).
|
|
53
|
+
- Transfer reversals are recorded with status `reversed` (was `failed`).
|
|
54
|
+
- `list()` no longer eagerly loads every page into memory; it returns a single
|
|
55
|
+
page (use `iter_all()` to stream all records).
|
|
56
|
+
|
|
57
|
+
### Breaking changes
|
|
58
|
+
|
|
59
|
+
- Webhooks are rejected when no `SECRET_KEY`/`WEBHOOK_SECRET` is configured.
|
|
60
|
+
- `POST`/`PUT` requests are not auto-retried; retry writes yourself with a stable
|
|
61
|
+
`reference`.
|
|
62
|
+
- `list()` returns a single page instead of all pages; use `iter_all()`.
|
|
63
|
+
- `setup.py`/`setup.cfg` removed in favour of `pyproject.toml`.
|
|
64
|
+
|
|
65
|
+
See the documentation for migration guidance.
|
|
66
|
+
|
|
67
|
+
## [1.0.0] - 2024-02-13
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
|
|
71
|
+
- Initial release: Django integration for the Paystack payment gateway with
|
|
72
|
+
API clients, models, webhook handling, signals, and management commands.
|
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: paystack-django
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: A comprehensive Django integration for Paystack Payment Gateway
|
|
5
|
+
Author-email: Humming Byte <dev@hummingbyte.org>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/HummingByteDev/paystack-django
|
|
8
|
+
Project-URL: Documentation, https://django-paystack.readthedocs.io
|
|
9
|
+
Project-URL: Repository, https://github.com/HummingByteDev/paystack-django.git
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/HummingByteDev/paystack-django/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/HummingByteDev/paystack-django/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: django,paystack,payment,payment-gateway,nigerian-payment
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Environment :: Web Environment
|
|
15
|
+
Classifier: Framework :: Django
|
|
16
|
+
Classifier: Framework :: Django :: 3.2
|
|
17
|
+
Classifier: Framework :: Django :: 4.0
|
|
18
|
+
Classifier: Framework :: Django :: 4.1
|
|
19
|
+
Classifier: Framework :: Django :: 4.2
|
|
20
|
+
Classifier: Framework :: Django :: 5.0
|
|
21
|
+
Classifier: Framework :: Django :: 5.2
|
|
22
|
+
Classifier: Framework :: Django :: 6.0
|
|
23
|
+
Classifier: Intended Audience :: Developers
|
|
24
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
25
|
+
Classifier: Natural Language :: English
|
|
26
|
+
Classifier: Operating System :: OS Independent
|
|
27
|
+
Classifier: Programming Language :: Python
|
|
28
|
+
Classifier: Programming Language :: Python :: 3
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
35
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
36
|
+
Requires-Python: >=3.8
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
License-File: LICENSE
|
|
39
|
+
Requires-Dist: Django>=3.2
|
|
40
|
+
Requires-Dist: requests>=2.32.0
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
43
|
+
Requires-Dist: pytest-django>=4.5; extra == "dev"
|
|
44
|
+
Requires-Dist: pytest-cov>=3.0; extra == "dev"
|
|
45
|
+
Requires-Dist: black>=22.0; extra == "dev"
|
|
46
|
+
Requires-Dist: flake8>=4.0; extra == "dev"
|
|
47
|
+
Requires-Dist: isort>=5.10; extra == "dev"
|
|
48
|
+
Requires-Dist: mypy>=0.950; extra == "dev"
|
|
49
|
+
Requires-Dist: django-stubs>=1.12.0; extra == "dev"
|
|
50
|
+
Requires-Dist: types-requests>=2.25.0; extra == "dev"
|
|
51
|
+
Requires-Dist: tox>=3.24; extra == "dev"
|
|
52
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
53
|
+
Requires-Dist: bandit>=1.7.0; extra == "dev"
|
|
54
|
+
Provides-Extra: docs
|
|
55
|
+
Requires-Dist: sphinx>=4.5; extra == "docs"
|
|
56
|
+
Requires-Dist: sphinx-rtd-theme>=1.0; extra == "docs"
|
|
57
|
+
Requires-Dist: sphinx-autodoc-typehints>=1.18; extra == "docs"
|
|
58
|
+
Dynamic: license-file
|
|
59
|
+
|
|
60
|
+
# paystack-django
|
|
61
|
+
|
|
62
|
+
A comprehensive Django integration for the **Paystack Payment Gateway**. This package provides a complete, production-ready solution for integrating Paystack payments into your Django applications.
|
|
63
|
+
|
|
64
|
+
[](https://badge.fury.io/py/paystack-django)
|
|
65
|
+
[](https://www.djangoproject.com)
|
|
66
|
+
[](https://www.python.org)
|
|
67
|
+
[](https://opensource.org/licenses/MIT)
|
|
68
|
+
|
|
69
|
+
## Features
|
|
70
|
+
|
|
71
|
+
- **Broad Paystack API Coverage** - Most Paystack endpoints across 25+ categories
|
|
72
|
+
- **Django Models** - Pre-built models for transactions, customers, plans, and more
|
|
73
|
+
- **Webhook Support** - Built-in webhook handling and HMAC-SHA512 signature verification (fails closed)
|
|
74
|
+
- **Signal Support** - Django signals for payment events
|
|
75
|
+
- **Type Hints** - Typed public interface with a shipped `py.typed` marker
|
|
76
|
+
- **Comprehensive Documentation** - Detailed docs and examples
|
|
77
|
+
- **Production Ready** - Used in production by multiple companies
|
|
78
|
+
|
|
79
|
+
## Supported Services
|
|
80
|
+
|
|
81
|
+
The package provides Django-native clients for the following Paystack APIs.
|
|
82
|
+
"Full" means every endpoint in that category is implemented.
|
|
83
|
+
|
|
84
|
+
| Category | Status |
|
|
85
|
+
|---|---|
|
|
86
|
+
| Transactions | Full |
|
|
87
|
+
| Transaction Splits | Full |
|
|
88
|
+
| Customers (incl. authorization & direct-debit onboarding) | Full |
|
|
89
|
+
| Plans / Subscriptions | Full |
|
|
90
|
+
| Products | Full |
|
|
91
|
+
| Payment Pages / Payment Requests | Full |
|
|
92
|
+
| Transfers / Transfer Recipients / Transfer Control | Full |
|
|
93
|
+
| Refunds | Full |
|
|
94
|
+
| Disputes | Full |
|
|
95
|
+
| Subaccounts | Full |
|
|
96
|
+
| Dedicated Virtual Accounts | Full |
|
|
97
|
+
| Terminal | Full |
|
|
98
|
+
| Virtual Terminal | Full |
|
|
99
|
+
| Direct Debit | Full |
|
|
100
|
+
| Bulk Charges | Full |
|
|
101
|
+
| Charge | Full |
|
|
102
|
+
| Verification (Bank) | Full |
|
|
103
|
+
| Settlements | Full |
|
|
104
|
+
| Integration | Full |
|
|
105
|
+
| Apple Pay | Full |
|
|
106
|
+
| Orders | Full |
|
|
107
|
+
| Storefronts | Full |
|
|
108
|
+
| Miscellaneous (banks, countries, states) | Full |
|
|
109
|
+
|
|
110
|
+
> Some list endpoints currently support page-based pagination; cursor-based
|
|
111
|
+
> pagination is on the roadmap. See the parity matrix for per-endpoint detail.
|
|
112
|
+
|
|
113
|
+
## Installation
|
|
114
|
+
|
|
115
|
+
Install using pip:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pip install paystack-django
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Or install from source:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
git clone https://github.com/HummingByteDev/paystack-django.git
|
|
125
|
+
cd django-paystack
|
|
126
|
+
pip install -e .
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Quick Start
|
|
130
|
+
|
|
131
|
+
### 1. Add to Django Settings
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
# settings.py
|
|
135
|
+
|
|
136
|
+
INSTALLED_APPS = [
|
|
137
|
+
# ...
|
|
138
|
+
'djpaystack',
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
PAYSTACK = {
|
|
142
|
+
'SECRET_KEY': 'sk_live_your_secret_key_here',
|
|
143
|
+
'PUBLIC_KEY': 'pk_live_your_public_key_here',
|
|
144
|
+
# Paystack signs webhooks with your API SECRET KEY (the same sk_... value).
|
|
145
|
+
# Set this to that key; if left unset, webhooks are REJECTED (fail closed).
|
|
146
|
+
'WEBHOOK_SECRET': 'sk_live_your_secret_key_here',
|
|
147
|
+
'ENVIRONMENT': 'production', # or 'test'
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 2. Create PaystackClient Instance
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from djpaystack import PaystackClient
|
|
155
|
+
|
|
156
|
+
client = PaystackClient()
|
|
157
|
+
|
|
158
|
+
# Initialize a transaction
|
|
159
|
+
response = client.transaction.initialize(
|
|
160
|
+
email='customer@example.com',
|
|
161
|
+
amount=50000, # in kobo (500 NGN)
|
|
162
|
+
reference='unique-reference-123'
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
authorization_url = response['data']['authorization_url']
|
|
166
|
+
print(f"Redirect user to: {authorization_url}")
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 3. Verify Transaction
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
# After user completes payment
|
|
173
|
+
verified = client.transaction.verify(reference='unique-reference-123')
|
|
174
|
+
|
|
175
|
+
if verified['data']['status'] == 'success':
|
|
176
|
+
print("Payment successful!")
|
|
177
|
+
# Update your database
|
|
178
|
+
else:
|
|
179
|
+
print("Payment failed!")
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 4. Set Up Webhooks
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
# urls.py
|
|
186
|
+
from django.urls import path
|
|
187
|
+
from djpaystack.webhooks import views as webhook_views
|
|
188
|
+
|
|
189
|
+
urlpatterns = [
|
|
190
|
+
path('webhooks/paystack/', webhook_views.handle_webhook, name='paystack_webhook'),
|
|
191
|
+
]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Then configure the webhook URL in your Paystack dashboard.
|
|
195
|
+
|
|
196
|
+
## Configuration
|
|
197
|
+
|
|
198
|
+
Complete configuration options available in `PAYSTACK` setting:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
PAYSTACK = {
|
|
202
|
+
# Required
|
|
203
|
+
'SECRET_KEY': 'your-secret-key', # Required
|
|
204
|
+
'PUBLIC_KEY': 'your-public-key', # Required
|
|
205
|
+
|
|
206
|
+
# Optional
|
|
207
|
+
'BASE_URL': 'https://api.paystack.co', # API base URL
|
|
208
|
+
# Paystack signs webhooks with your SECRET_KEY. Leave WEBHOOK_SECRET unset
|
|
209
|
+
# to use SECRET_KEY automatically; only set it to override.
|
|
210
|
+
'WEBHOOK_SECRET': None,
|
|
211
|
+
'WEBHOOK_SIGNATURE_REQUIRED': True, # reject unsigned webhooks (fail closed)
|
|
212
|
+
'CALLBACK_URL': 'https://yoursite.com/callback/', # Callback URL
|
|
213
|
+
'ENVIRONMENT': 'production', # 'production' or 'test'
|
|
214
|
+
'TIMEOUT': 30, # Request timeout in seconds
|
|
215
|
+
'MAX_RETRIES': 3, # Number of retries
|
|
216
|
+
'VERIFY_SSL': True, # Verify SSL certificates
|
|
217
|
+
'CURRENCY': 'NGN', # Default currency
|
|
218
|
+
'AUTO_VERIFY_TRANSACTIONS': True, # Auto-verify on webhook
|
|
219
|
+
'CACHE_TIMEOUT': 300, # Cache timeout in seconds
|
|
220
|
+
'LOG_REQUESTS': False, # Log API requests
|
|
221
|
+
'LOG_RESPONSES': False, # Log API responses
|
|
222
|
+
'ENABLE_SIGNALS': True, # Enable Django signals
|
|
223
|
+
'ENABLE_MODELS': True, # Enable Django models
|
|
224
|
+
'ALLOWED_WEBHOOK_IPS': [], # Allowed webhook IPs (empty = all)
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Usage Examples
|
|
229
|
+
|
|
230
|
+
### Transactions
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from djpaystack import PaystackClient
|
|
234
|
+
|
|
235
|
+
client = PaystackClient()
|
|
236
|
+
|
|
237
|
+
# Initialize transaction
|
|
238
|
+
response = client.transaction.initialize(
|
|
239
|
+
email='user@example.com',
|
|
240
|
+
amount=100000,
|
|
241
|
+
reference='unique-ref-001',
|
|
242
|
+
metadata={'order_id': 123}
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Verify transaction
|
|
246
|
+
response = client.transaction.verify(reference='unique-ref-001')
|
|
247
|
+
|
|
248
|
+
# List transactions
|
|
249
|
+
response = client.transaction.list(page=1, per_page=10)
|
|
250
|
+
|
|
251
|
+
# Fetch transaction
|
|
252
|
+
response = client.transaction.fetch(id=123456)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Customers
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
# Create customer
|
|
259
|
+
response = client.customer.create(
|
|
260
|
+
email='customer@example.com',
|
|
261
|
+
first_name='John',
|
|
262
|
+
last_name='Doe',
|
|
263
|
+
phone='1234567890'
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# List customers
|
|
267
|
+
response = client.customer.list(page=1, per_page=50)
|
|
268
|
+
|
|
269
|
+
# Fetch customer
|
|
270
|
+
response = client.customer.fetch(customer_code='CUS_xxxxx')
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Subscriptions
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
# Create subscription
|
|
277
|
+
response = client.subscription.create(
|
|
278
|
+
customer_code='CUS_xxxxx',
|
|
279
|
+
plan_code='PLN_xxxxx',
|
|
280
|
+
authorization_code='AUTH_xxxxx'
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Enable subscription
|
|
284
|
+
response = client.subscription.enable(
|
|
285
|
+
code='SUB_xxxxx',
|
|
286
|
+
token='tok_xxxxx'
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Disable subscription
|
|
290
|
+
response = client.subscription.disable(code='SUB_xxxxx')
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Plans
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
# Create plan
|
|
297
|
+
response = client.plan.create(
|
|
298
|
+
name='Monthly Plan',
|
|
299
|
+
description='Premium monthly subscription',
|
|
300
|
+
amount=500000, # 5000 NGN
|
|
301
|
+
interval='monthly',
|
|
302
|
+
plan_code='PLN_custom'
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# List plans
|
|
306
|
+
response = client.plan.list(page=1)
|
|
307
|
+
|
|
308
|
+
# Fetch plan
|
|
309
|
+
response = client.plan.fetch(plan_id=123)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Transfers
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
# Create transfer recipient
|
|
316
|
+
response = client.transfer_recipient.create(
|
|
317
|
+
type='nuban',
|
|
318
|
+
name='John Doe',
|
|
319
|
+
account_number='0000000000',
|
|
320
|
+
bank_code='001'
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# Initiate transfer
|
|
324
|
+
response = client.transfer.initiate(
|
|
325
|
+
source='balance',
|
|
326
|
+
amount=50000,
|
|
327
|
+
recipient='RCP_xxxxx',
|
|
328
|
+
reference='transfer-001'
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Finalize transfer
|
|
332
|
+
response = client.transfer.finalize(transfer_code='TRF_xxxxx', otp='123456')
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Refunds
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
# Create refund
|
|
339
|
+
response = client.refund.create(
|
|
340
|
+
transaction='123456'
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# List refunds
|
|
344
|
+
response = client.refund.list(page=1)
|
|
345
|
+
|
|
346
|
+
# Fetch refund
|
|
347
|
+
response = client.refund.fetch(refund_id='123')
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Database Models
|
|
351
|
+
|
|
352
|
+
The package includes Django models for persistence:
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
from djpaystack.models import (
|
|
356
|
+
PaystackTransaction,
|
|
357
|
+
PaystackCustomer,
|
|
358
|
+
PaystackPlan,
|
|
359
|
+
PaystackProduct,
|
|
360
|
+
PaystackRefund,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
# Query transactions
|
|
364
|
+
transactions = PaystackTransaction.objects.filter(status='success')
|
|
365
|
+
|
|
366
|
+
# Transactions by customer
|
|
367
|
+
customer_transactions = PaystackTransaction.objects.filter(
|
|
368
|
+
customer_email='user@example.com'
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
# Create a payment request
|
|
372
|
+
from djpaystack.models import PaymentRequest
|
|
373
|
+
request = PaymentRequest.objects.create(
|
|
374
|
+
reference='req-001',
|
|
375
|
+
amount=100000,
|
|
376
|
+
description='Course enrollment'
|
|
377
|
+
)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Webhooks
|
|
381
|
+
|
|
382
|
+
Handle Paystack webhooks automatically:
|
|
383
|
+
|
|
384
|
+
```python
|
|
385
|
+
# Webhook signals are automatically sent
|
|
386
|
+
from djpaystack.signals import transaction_verified, transaction_failed
|
|
387
|
+
|
|
388
|
+
from django.dispatch import receiver
|
|
389
|
+
|
|
390
|
+
@receiver(transaction_verified)
|
|
391
|
+
def on_payment_success(sender, transaction, **kwargs):
|
|
392
|
+
print(f"Payment successful: {transaction.reference}")
|
|
393
|
+
# Update your application
|
|
394
|
+
|
|
395
|
+
@receiver(transaction_failed)
|
|
396
|
+
def on_payment_failed(sender, transaction, **kwargs):
|
|
397
|
+
print(f"Payment failed: {transaction.reference}")
|
|
398
|
+
# Handle failed payment
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Testing
|
|
402
|
+
|
|
403
|
+
Run the test suite:
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
pip install -e ".[dev]"
|
|
407
|
+
pytest
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
With coverage:
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
pytest --cov=djpaystack
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Run tests across Python versions:
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
tox
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Django Compatibility
|
|
423
|
+
|
|
424
|
+
| Package Version | Django 3.2 | Django 4.0 | Django 4.1 | Django 4.2 | Django 5.0 | Django 5.2 | Django 6.0 |
|
|
425
|
+
| --------------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- |
|
|
426
|
+
| 1.0.x | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
427
|
+
|
|
428
|
+
## Python Compatibility
|
|
429
|
+
|
|
430
|
+
- Python 3.8
|
|
431
|
+
- Python 3.9
|
|
432
|
+
- Python 3.10
|
|
433
|
+
- Python 3.11
|
|
434
|
+
- Python 3.12
|
|
435
|
+
- Python 3.13
|
|
436
|
+
- Python 3.14
|
|
437
|
+
|
|
438
|
+
## Environment Variables
|
|
439
|
+
|
|
440
|
+
You can also configure using environment variables:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
PAYSTACK_SECRET_KEY=sk_live_xxx
|
|
444
|
+
PAYSTACK_PUBLIC_KEY=pk_live_xxx
|
|
445
|
+
# Webhooks are signed with your secret key; use the same sk_... value here.
|
|
446
|
+
PAYSTACK_WEBHOOK_SECRET=sk_live_xxx
|
|
447
|
+
PAYSTACK_ENVIRONMENT=production
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Then use `python-decouple` to load them:
|
|
451
|
+
|
|
452
|
+
```python
|
|
453
|
+
from decouple import config
|
|
454
|
+
|
|
455
|
+
PAYSTACK = {
|
|
456
|
+
'SECRET_KEY': config('PAYSTACK_SECRET_KEY'),
|
|
457
|
+
'PUBLIC_KEY': config('PAYSTACK_PUBLIC_KEY'),
|
|
458
|
+
'WEBHOOK_SECRET': config('PAYSTACK_WEBHOOK_SECRET'),
|
|
459
|
+
'ENVIRONMENT': config('PAYSTACK_ENVIRONMENT', default='test'),
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
## Error Handling
|
|
464
|
+
|
|
465
|
+
The package provides specific exception classes:
|
|
466
|
+
|
|
467
|
+
```python
|
|
468
|
+
from djpaystack.exceptions import (
|
|
469
|
+
PaystackError,
|
|
470
|
+
PaystackAPIError,
|
|
471
|
+
PaystackValidationError,
|
|
472
|
+
PaystackAuthenticationError,
|
|
473
|
+
PaystackNetworkError,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
try:
|
|
477
|
+
client.transaction.verify(reference='ref-123')
|
|
478
|
+
except PaystackAuthenticationError:
|
|
479
|
+
print("Invalid API credentials")
|
|
480
|
+
except PaystackNetworkError:
|
|
481
|
+
print("Network error occurred")
|
|
482
|
+
except PaystackAPIError as e:
|
|
483
|
+
print(f"API error: {e}")
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
## Pagination
|
|
487
|
+
|
|
488
|
+
`list()` returns a **single page** (the first by default) and preserves
|
|
489
|
+
Paystack's `meta` block, so you control how much you fetch:
|
|
490
|
+
|
|
491
|
+
```python
|
|
492
|
+
response = client.transactions.list(
|
|
493
|
+
page=1,
|
|
494
|
+
per_page=50,
|
|
495
|
+
from_date='2024-01-01',
|
|
496
|
+
to_date='2024-12-31',
|
|
497
|
+
status='success',
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
transactions = response['data'] # this page's records
|
|
501
|
+
meta = response['meta'] # {'page', 'pageCount', 'total', ...}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
To stream **every** record across all pages without loading them all into
|
|
505
|
+
memory, use the lazy iterator:
|
|
506
|
+
|
|
507
|
+
```python
|
|
508
|
+
for txn in client.transactions.iter_all(status='success', from_date='2024-01-01'):
|
|
509
|
+
process(txn) # one record at a time; pages fetched on demand
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
> **Upgrading from 1.0.x?** Previously `list()` eagerly fetched *all* pages.
|
|
513
|
+
> It now returns one page — switch full scans to `iter_all()`. See the
|
|
514
|
+
> [CHANGELOG](CHANGELOG.md) for the full list of breaking changes.
|
|
515
|
+
|
|
516
|
+
## Logging
|
|
517
|
+
|
|
518
|
+
Enable logging to debug API interactions:
|
|
519
|
+
|
|
520
|
+
```python
|
|
521
|
+
import logging
|
|
522
|
+
|
|
523
|
+
# In settings.py
|
|
524
|
+
PAYSTACK = {
|
|
525
|
+
'LOG_REQUESTS': True,
|
|
526
|
+
'LOG_RESPONSES': True,
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
# Configure logging
|
|
530
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
531
|
+
logger = logging.getLogger('djpaystack')
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## Security
|
|
535
|
+
|
|
536
|
+
### Environment Variables
|
|
537
|
+
|
|
538
|
+
Never hardcode secrets:
|
|
539
|
+
|
|
540
|
+
```python
|
|
541
|
+
import os
|
|
542
|
+
from decouple import config
|
|
543
|
+
|
|
544
|
+
PAYSTACK = {
|
|
545
|
+
'SECRET_KEY': config('PAYSTACK_SECRET_KEY'),
|
|
546
|
+
'PUBLIC_KEY': config('PAYSTACK_PUBLIC_KEY'),
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Webhook Verification
|
|
551
|
+
|
|
552
|
+
Verify all incoming webhooks:
|
|
553
|
+
|
|
554
|
+
```python
|
|
555
|
+
from djpaystack.webhooks.handlers import verify_webhook_signature
|
|
556
|
+
|
|
557
|
+
is_valid = verify_webhook_signature(
|
|
558
|
+
body=request.body,
|
|
559
|
+
signature_header=request.META.get('HTTP_X_PAYSTACK_SIGNATURE'),
|
|
560
|
+
webhook_secret=PAYSTACK['WEBHOOK_SECRET']
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
if not is_valid:
|
|
564
|
+
return JsonResponse({'status': 'invalid'}, status=403)
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
## Contributing
|
|
568
|
+
|
|
569
|
+
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
570
|
+
|
|
571
|
+
## Support
|
|
572
|
+
|
|
573
|
+
- 📚 [Full Documentation](https://paystack-django.readthedocs.io/)
|
|
574
|
+
- 🐛 [Report Issues](https://github.com/HummingByteDev/paystack-django/issues)
|
|
575
|
+
- 💬 [Discussions](https://github.com/HummingByteDev/paystack-django/discussions)
|
|
576
|
+
- 📧 [Email Support](mailto:dev@hummingbyte.org)
|
|
577
|
+
|
|
578
|
+
## Changelog
|
|
579
|
+
|
|
580
|
+
See [CHANGELOG.md](CHANGELOG.md) for detailed release notes.
|
|
581
|
+
|
|
582
|
+
## License
|
|
583
|
+
|
|
584
|
+
This project is licensed under the MIT License - see [LICENSE](LICENSE) file for details.
|
|
585
|
+
|
|
586
|
+
## Acknowledgments
|
|
587
|
+
|
|
588
|
+
- [Paystack](https://paystack.com) for the excellent payment gateway
|
|
589
|
+
- Django community for the amazing framework
|
|
590
|
+
- All contributors and users of this package
|
|
591
|
+
|
|
592
|
+
## Disclaimer
|
|
593
|
+
|
|
594
|
+
This package is not affiliated with or endorsed by Paystack. It is maintained by Humming Byte as a community contribution.
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
**Made with ❤️ by [Humming Byte](https://hummingbyte.org)**
|