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