python-getpaid-core 0.1.1__tar.gz → 3.0.0a3__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 (80) hide show
  1. python_getpaid_core-3.0.0a3/.github/workflows/ci.yml +33 -0
  2. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.gitignore +1 -0
  3. python_getpaid_core-3.0.0a3/.sisyphus/evidence/task-22-readme-core.txt +7 -0
  4. python_getpaid_core-3.0.0a3/PKG-INFO +107 -0
  5. python_getpaid_core-3.0.0a3/README.md +82 -0
  6. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/docs/changelog.md +5 -6
  7. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/docs/concepts.md +28 -36
  8. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/docs/getting-started.md +21 -12
  9. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/docs/reference.md +1 -1
  10. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/pyproject.toml +4 -1
  11. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/src/getpaid_core/__init__.py +15 -1
  12. python_getpaid_core-3.0.0a3/src/getpaid_core/backends/dummy.py +145 -0
  13. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/src/getpaid_core/enums.py +23 -4
  14. python_getpaid_core-3.0.0a3/src/getpaid_core/flow.py +203 -0
  15. python_getpaid_core-3.0.0a3/src/getpaid_core/fsm.py +261 -0
  16. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/src/getpaid_core/processor.py +20 -28
  17. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/src/getpaid_core/protocols.py +5 -8
  18. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/src/getpaid_core/registry.py +11 -15
  19. python_getpaid_core-3.0.0a3/src/getpaid_core/types.py +102 -0
  20. python_getpaid_core-3.0.0a3/src/getpaid_core/validators.py +13 -0
  21. python_getpaid_core-3.0.0a3/tests/conftest.py +211 -0
  22. python_getpaid_core-3.0.0a3/tests/test_dummy_backend.py +132 -0
  23. python_getpaid_core-3.0.0a3/tests/test_enums.py +47 -0
  24. python_getpaid_core-3.0.0a3/tests/test_flow.py +220 -0
  25. python_getpaid_core-3.0.0a3/tests/test_integration.py +79 -0
  26. python_getpaid_core-3.0.0a3/tests/test_processor.py +129 -0
  27. python_getpaid_core-3.0.0a3/tests/test_protocols.py +89 -0
  28. python_getpaid_core-3.0.0a3/tests/test_public_api.py +25 -0
  29. python_getpaid_core-3.0.0a3/tests/test_registry.py +114 -0
  30. python_getpaid_core-3.0.0a3/tests/test_state_engine.py +122 -0
  31. python_getpaid_core-3.0.0a3/tests/test_types.py +84 -0
  32. python_getpaid_core-3.0.0a3/tests/test_version.py +11 -0
  33. python_getpaid_core-0.1.1/PKG-INFO +0 -105
  34. python_getpaid_core-0.1.1/README.md +0 -80
  35. python_getpaid_core-0.1.1/src/getpaid_core/backends/dummy.py +0 -95
  36. python_getpaid_core-0.1.1/src/getpaid_core/flow.py +0 -144
  37. python_getpaid_core-0.1.1/src/getpaid_core/fsm.py +0 -223
  38. python_getpaid_core-0.1.1/src/getpaid_core/types.py +0 -52
  39. python_getpaid_core-0.1.1/src/getpaid_core/validators.py +0 -21
  40. python_getpaid_core-0.1.1/tests/conftest.py +0 -132
  41. python_getpaid_core-0.1.1/tests/test_dummy_backend.py +0 -152
  42. python_getpaid_core-0.1.1/tests/test_enums.py +0 -87
  43. python_getpaid_core-0.1.1/tests/test_flow.py +0 -208
  44. python_getpaid_core-0.1.1/tests/test_fsm.py +0 -292
  45. python_getpaid_core-0.1.1/tests/test_integration.py +0 -182
  46. python_getpaid_core-0.1.1/tests/test_processor.py +0 -184
  47. python_getpaid_core-0.1.1/tests/test_protocols.py +0 -85
  48. python_getpaid_core-0.1.1/tests/test_public_api.py +0 -35
  49. python_getpaid_core-0.1.1/tests/test_registry.py +0 -190
  50. python_getpaid_core-0.1.1/tests/test_types.py +0 -97
  51. python_getpaid_core-0.1.1/uv.lock +0 -936
  52. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.cookiecutter.json +0 -0
  53. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.gitattributes +0 -0
  54. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.github/dependabot.yml +0 -0
  55. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.github/labels.yml +0 -0
  56. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.github/release-drafter.yml +0 -0
  57. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.github/workflows/constraints.txt +0 -0
  58. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.github/workflows/labeler.yml +0 -0
  59. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.github/workflows/release.yml +0 -0
  60. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.github/workflows/tests.yml +0 -0
  61. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.plans/2026-02-13-getpaid-core-design.md +0 -0
  62. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.plans/2026-02-13-getpaid-core-implementation.md +0 -0
  63. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.pre-commit-config.yaml +0 -0
  64. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/.readthedocs.yml +0 -0
  65. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/CODE_OF_CONDUCT.md +0 -0
  66. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/CONTRIBUTING.md +0 -0
  67. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/LICENSE +0 -0
  68. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/codecov.yml +0 -0
  69. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/docs/codeofconduct.md +0 -0
  70. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/docs/conf.py +0 -0
  71. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/docs/contributing.md +0 -0
  72. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/docs/index.md +0 -0
  73. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/docs/license.md +0 -0
  74. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/docs/requirements.txt +0 -0
  75. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/src/getpaid_core/backends/__init__.py +0 -0
  76. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/src/getpaid_core/exceptions.py +0 -0
  77. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/src/getpaid_core/py.typed +0 -0
  78. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/tests/__init__.py +0 -0
  79. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/tests/test_exceptions.py +0 -0
  80. {python_getpaid_core-0.1.1 → python_getpaid_core-3.0.0a3}/tests/test_validators.py +0 -0
@@ -0,0 +1,33 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.12", "3.13"]
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python ${{ matrix.python-version }}
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+
23
+ - name: Install uv
24
+ run: pip install uv
25
+
26
+ - name: Install dependencies
27
+ run: uv sync
28
+
29
+ - name: Lint with ruff
30
+ run: uv run ruff check .
31
+
32
+ - name: Run tests
33
+ run: uv run pytest --tb=short
@@ -9,3 +9,4 @@
9
9
  /src/*.egg-info/
10
10
  __pycache__/
11
11
  /.idea/
12
+ /uv.lock
@@ -0,0 +1,7 @@
1
+ README.md verification results:
2
+ Line count: 82
3
+ pip install count: 1
4
+ BaseProcessor count: 5
5
+ Wrappers count: 8
6
+
7
+ All verification checks PASSED.
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-getpaid-core
3
+ Version: 3.0.0a3
4
+ Summary: Framework-agnostic payment processing core.
5
+ Project-URL: Homepage, https://github.com/django-getpaid/python-getpaid-core
6
+ Project-URL: Repository, https://github.com/django-getpaid/python-getpaid-core
7
+ Project-URL: Documentation, https://getpaid-core.readthedocs.io
8
+ Project-URL: Changelog, https://github.com/django-getpaid/python-getpaid-core/releases
9
+ Author-email: Dominik Kozaczko <dominik@kozaczko.info>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Office/Business :: Financial
18
+ Classifier: Topic :: Office/Business :: Financial :: Point-Of-Sale
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: anyio>=4.0
22
+ Requires-Dist: httpx>=0.27.0
23
+ Requires-Dist: transitions>=0.9.0
24
+ Description-Content-Type: text/markdown
25
+
26
+ # python-getpaid-core
27
+
28
+ [![PyPI version](https://img.shields.io/pypi/v/python-getpaid-core)](https://pypi.org/project/python-getpaid-core/)
29
+ [![Python version](https://img.shields.io/pypi/pyversions/python-getpaid-core)](https://pypi.org/project/python-getpaid-core/)
30
+ [![License](https://img.shields.io/pypi/l/python-getpaid-core)](https://github.com/django-getpaid/python-getpaid-core/blob/main/LICENSE)
31
+
32
+ **Framework-agnostic payment processing core.**
33
+
34
+ `python-getpaid-core` is the foundation of the Getpaid ecosystem. It provides the abstract interfaces, semantic payment update engine, and plugin registry needed to build a robust payment system without coupling your logic to a specific web framework or payment provider.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install python-getpaid-core
40
+ ```
41
+
42
+ ## Quick Start: Creating a Custom Processor
43
+
44
+ To implement a new payment backend, subclass `BaseProcessor` and implement at least `prepare_transaction`.
45
+
46
+ ```python
47
+ from getpaid_core import BaseProcessor
48
+ from getpaid_core.types import TransactionResult
49
+
50
+ class MyPaymentProcessor(BaseProcessor):
51
+ slug = "my-provider"
52
+ display_name = "My Payment Provider"
53
+ accepted_currencies = ["USD", "EUR"]
54
+
55
+ async def prepare_transaction(self, **kwargs) -> TransactionResult:
56
+ # Generate payment link or form data
57
+ return TransactionResult(
58
+ redirect_url=f"https://api.provider.com/pay/{self.payment.id}",
59
+ method="GET"
60
+ )
61
+ ```
62
+
63
+ ### Registering your Processor
64
+
65
+ Register your processor using entry points in your `pyproject.toml` so it can be discovered by the registry:
66
+
67
+ ```toml
68
+ [project.entry-points."getpaid.backends"]
69
+ my-provider = "my_package.processors:MyPaymentProcessor"
70
+ ```
71
+
72
+ ## Architecture Overview
73
+
74
+ - **BaseProcessor**: The abstract base class that all payment gateway plugins must implement. It provides the standard interface for transaction preparation, callback handling, charging, and refunds.
75
+ - **PaymentFlow**: Manages the payment lifecycle using semantic payment update objects. It ensures that payments move between states (e.g., `NEW` -> `PREPARED` -> `PAID`) according to strict business rules.
76
+ - **PluginRegistry**: A central service for discovering and managing payment processors registered via `getpaid.backends` entry points.
77
+ - **State Engine**: Applies semantic payment and fraud events to payment objects, merges provider metadata, and tracks idempotent provider event IDs.
78
+
79
+ ## API Summary
80
+
81
+ | Class / Module | Role |
82
+ | --- | --- |
83
+ | `BaseProcessor` | Abstract base for implementing payment gateways. |
84
+ | `PaymentFlow` | Semantic orchestration for payment lifecycles. |
85
+ | `PaymentStatus` | Enum for all possible payment states (NEW, PAID, FAILED, etc.). |
86
+ | `registry` | Singleton registry for backend discovery. |
87
+ | `TransactionResult` | Standard response for transaction initiation. |
88
+ | `GetPaidException` | Base exception for all payment-related errors. |
89
+
90
+ ## Ecosystem
91
+
92
+ `getpaid-core` is the heart of a larger ecosystem designed to make payment processing easy in any Python web application.
93
+
94
+ ### Framework Wrappers
95
+ - [django-getpaid](https://github.com/django-getpaid/django-getpaid) — Official Django integration.
96
+ - [litestar-getpaid](https://github.com/django-getpaid/litestar-getpaid) — Official Litestar integration.
97
+ - [fastapi-getpaid](https://github.com/django-getpaid/fastapi-getpaid) — Official FastAPI integration.
98
+
99
+ ### Processor Plugins
100
+ - [python-getpaid-payu](https://github.com/django-getpaid/python-getpaid-payu) — PayU backend.
101
+ - [python-getpaid-paynow](https://github.com/django-getpaid/python-getpaid-paynow) — Paynow backend.
102
+ - [python-getpaid-bitpay](https://github.com/django-getpaid/python-getpaid-bitpay) — BitPay backend.
103
+ - [python-getpaid-przelewy24](https://github.com/django-getpaid/python-getpaid-przelewy24) — Przelewy24 backend.
104
+
105
+ ## License
106
+
107
+ This project is licensed under the MIT License.
@@ -0,0 +1,82 @@
1
+ # python-getpaid-core
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/python-getpaid-core)](https://pypi.org/project/python-getpaid-core/)
4
+ [![Python version](https://img.shields.io/pypi/pyversions/python-getpaid-core)](https://pypi.org/project/python-getpaid-core/)
5
+ [![License](https://img.shields.io/pypi/l/python-getpaid-core)](https://github.com/django-getpaid/python-getpaid-core/blob/main/LICENSE)
6
+
7
+ **Framework-agnostic payment processing core.**
8
+
9
+ `python-getpaid-core` is the foundation of the Getpaid ecosystem. It provides the abstract interfaces, semantic payment update engine, and plugin registry needed to build a robust payment system without coupling your logic to a specific web framework or payment provider.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install python-getpaid-core
15
+ ```
16
+
17
+ ## Quick Start: Creating a Custom Processor
18
+
19
+ To implement a new payment backend, subclass `BaseProcessor` and implement at least `prepare_transaction`.
20
+
21
+ ```python
22
+ from getpaid_core import BaseProcessor
23
+ from getpaid_core.types import TransactionResult
24
+
25
+ class MyPaymentProcessor(BaseProcessor):
26
+ slug = "my-provider"
27
+ display_name = "My Payment Provider"
28
+ accepted_currencies = ["USD", "EUR"]
29
+
30
+ async def prepare_transaction(self, **kwargs) -> TransactionResult:
31
+ # Generate payment link or form data
32
+ return TransactionResult(
33
+ redirect_url=f"https://api.provider.com/pay/{self.payment.id}",
34
+ method="GET"
35
+ )
36
+ ```
37
+
38
+ ### Registering your Processor
39
+
40
+ Register your processor using entry points in your `pyproject.toml` so it can be discovered by the registry:
41
+
42
+ ```toml
43
+ [project.entry-points."getpaid.backends"]
44
+ my-provider = "my_package.processors:MyPaymentProcessor"
45
+ ```
46
+
47
+ ## Architecture Overview
48
+
49
+ - **BaseProcessor**: The abstract base class that all payment gateway plugins must implement. It provides the standard interface for transaction preparation, callback handling, charging, and refunds.
50
+ - **PaymentFlow**: Manages the payment lifecycle using semantic payment update objects. It ensures that payments move between states (e.g., `NEW` -> `PREPARED` -> `PAID`) according to strict business rules.
51
+ - **PluginRegistry**: A central service for discovering and managing payment processors registered via `getpaid.backends` entry points.
52
+ - **State Engine**: Applies semantic payment and fraud events to payment objects, merges provider metadata, and tracks idempotent provider event IDs.
53
+
54
+ ## API Summary
55
+
56
+ | Class / Module | Role |
57
+ | --- | --- |
58
+ | `BaseProcessor` | Abstract base for implementing payment gateways. |
59
+ | `PaymentFlow` | Semantic orchestration for payment lifecycles. |
60
+ | `PaymentStatus` | Enum for all possible payment states (NEW, PAID, FAILED, etc.). |
61
+ | `registry` | Singleton registry for backend discovery. |
62
+ | `TransactionResult` | Standard response for transaction initiation. |
63
+ | `GetPaidException` | Base exception for all payment-related errors. |
64
+
65
+ ## Ecosystem
66
+
67
+ `getpaid-core` is the heart of a larger ecosystem designed to make payment processing easy in any Python web application.
68
+
69
+ ### Framework Wrappers
70
+ - [django-getpaid](https://github.com/django-getpaid/django-getpaid) — Official Django integration.
71
+ - [litestar-getpaid](https://github.com/django-getpaid/litestar-getpaid) — Official Litestar integration.
72
+ - [fastapi-getpaid](https://github.com/django-getpaid/fastapi-getpaid) — Official FastAPI integration.
73
+
74
+ ### Processor Plugins
75
+ - [python-getpaid-payu](https://github.com/django-getpaid/python-getpaid-payu) — PayU backend.
76
+ - [python-getpaid-paynow](https://github.com/django-getpaid/python-getpaid-paynow) — Paynow backend.
77
+ - [python-getpaid-bitpay](https://github.com/django-getpaid/python-getpaid-bitpay) — BitPay backend.
78
+ - [python-getpaid-przelewy24](https://github.com/django-getpaid/python-getpaid-przelewy24) — Przelewy24 backend.
79
+
80
+ ## License
81
+
82
+ This project is licensed under the MIT License.
@@ -12,12 +12,11 @@ framework-agnostic library.
12
12
  - Fraud status enum (`FraudStatus`) with 4 states
13
13
  - Backend method and confirmation method enums
14
14
  - `BaseProcessor` abstract class for payment gateway plugins
15
- - Payment and fraud state machines using `transitions` library
16
- - Transition guards (`_require_fully_paid`, `_require_fully_refunded`)
17
- - Amount callbacks (`_store_locked_amount`, `_accumulate_paid_amount`)
18
- - Fraud message callback (`_store_fraud_message`)
15
+ - Semantic payment and fraud update engine
16
+ - Transition validation with `InvalidTransitionError`
17
+ - Provider metadata merging and callback idempotency tracking
19
18
  - `PluginRegistry` with entry-point discovery and manual registration
20
19
  - Runtime-checkable protocols: `Payment`, `Order`, `PaymentRepository`
21
- - Typed data structures: `BuyerInfo`, `ItemInfo`, `ChargeResponse`,
22
- `PaymentStatusResponse`, `TransactionResult`
20
+ - Dataclass response types: `BuyerInfo`, `ItemInfo`, `ChargeResult`,
21
+ `PaymentUpdate`, `RefundResult`, `TransactionResult`
23
22
  - Structured exception hierarchy with `context` support
@@ -16,45 +16,35 @@ Payments move through these states:
16
16
  | `REFUND_STARTED` | `"refund_started"` | Refund initiated |
17
17
  | `REFUNDED` | `"refunded"` | Fully refunded or lock released |
18
18
 
19
- ## Payment State Transitions
19
+ ## Payment Events
20
20
 
21
21
  ```
22
- NEW ──────────────────────► PREPARED
23
- │ │
24
- │ confirm_lock │ confirm_lock
25
- ▼ ▼
26
- PRE_AUTH ◄─────────────────────┘
27
-
28
- ├── confirm_charge_sent ──► IN_CHARGE
29
- │ │
30
- │ confirm_payment │ confirm_payment
31
- ▼ ▼
32
- PARTIAL ◄──────────────────────┘
33
-
34
- ├── mark_as_paid ──────────► PAID
35
-
36
- ├── start_refund ──────────► REFUND_STARTED
37
- │ │
38
- │ cancel_refund │ confirm_refund
39
- ◄─────────────────────────────┘
40
-
41
- └── mark_as_refunded ──────► REFUNDED
42
-
43
- NEW/PREPARED/PRE_AUTH ──fail──► FAILED
44
- PRE_AUTH ──release_lock──────► REFUNDED
22
+ prepared -> PREPARED
23
+ locked -> PRE_AUTH
24
+ charge_requested -> IN_CHARGE
25
+ payment_captured -> PARTIAL or PAID
26
+ failed -> FAILED
27
+ refund_requested -> REFUND_STARTED
28
+ refund_confirmed -> PARTIAL or REFUNDED
29
+ refund_cancelled -> active paid status
30
+ lock_released -> REFUNDED
45
31
  ```
46
32
 
47
- ### Transition Guards
33
+ ### Transition Rules
48
34
 
49
- Some transitions have guards that raise `MachineError` if conditions aren't met:
35
+ The state engine raises `InvalidTransitionError` when an event is incompatible
36
+ with the current payment state.
50
37
 
51
- - **`mark_as_paid`** requires `is_fully_paid()` `amount_paid >= amount_required`
52
- - **`mark_as_refunded`** requires `is_fully_refunded()` `amount_refunded >= amount_paid`
38
+ - You cannot capture a payment after it is already refunded.
39
+ - You cannot start a refund before the payment has been paid.
40
+ - Refund confirmation moves to `REFUNDED` only when `amount_refunded >= amount_paid`.
53
41
 
54
- ### Amount Callbacks
42
+ ### Amount Handling
55
43
 
56
- - **`confirm_lock`** stores the locked amount via `_store_locked_amount`
57
- - **`confirm_payment`** accumulates paid amount via `_accumulate_paid_amount`
44
+ - `locked_amount` stores a pre-authorized amount.
45
+ - `paid_amount` updates captured funds and may reduce `amount_locked`.
46
+ - `refunded_amount` tracks refund progress.
47
+ - `provider_data` stores provider-specific metadata such as refund IDs and applied callback IDs.
58
48
 
59
49
  ## Fraud Statuses
60
50
 
@@ -103,13 +93,14 @@ class Payment(Protocol):
103
93
  currency: str
104
94
  status: str
105
95
  backend: str
106
- external_id: str
107
- description: str
96
+ external_id: str | None
97
+ description: str | None
108
98
  amount_paid: Decimal
109
99
  amount_locked: Decimal
110
100
  amount_refunded: Decimal
111
101
  fraud_status: str
112
102
  fraud_message: str
103
+ provider_data: dict[str, Any]
113
104
  ```
114
105
 
115
106
  ### PaymentRepository Protocol
@@ -162,6 +153,7 @@ raise ChargeFailure("Gateway returned 500", context={"status_code": 500})
162
153
  |------|-------------|
163
154
  | `BuyerInfo` | TypedDict with `email`, `first_name`, `last_name`, `phone` (all optional) |
164
155
  | `ItemInfo` | TypedDict with `name`, `quantity`, `unit_price` |
165
- | `ChargeResponse` | TypedDict with `amount_charged`, `success`, `async_call` |
166
- | `PaymentStatusResponse` | TypedDict with `amount`, `status`, `external_id` (all optional) |
167
- | `TransactionResult` | TypedDict with `redirect_url`, `form_data`, `method`, `headers` |
156
+ | `ChargeResult` | Dataclass with `amount_charged`, `success`, `async_call`, `provider_data` |
157
+ | `PaymentUpdate` | Dataclass describing semantic payment/fraud events and amounts |
158
+ | `RefundResult` | Dataclass with refund amount and provider metadata |
159
+ | `TransactionResult` | Dataclass with redirect, method, external ID, and provider metadata |
@@ -77,21 +77,30 @@ from getpaid_core.registry import registry
77
77
  registry.register(MyGatewayProcessor)
78
78
  ```
79
79
 
80
- ## Payment State Machine
80
+ ## Payment Updates
81
81
 
82
- Payments move through states via an FSM powered by the `transitions` library.
83
- The machine attaches trigger methods directly to payment objects:
82
+ Payments move through states by applying semantic `PaymentUpdate` objects.
83
+ Processors return updates from callbacks and status polling, and the flow
84
+ applies them to the payment object:
84
85
 
85
86
  ```python
86
- from getpaid_core.fsm import create_payment_machine
87
-
88
- machine = create_payment_machine(payment)
87
+ from decimal import Decimal
89
88
 
90
- # Now payment has trigger methods:
91
- payment.confirm_prepared() # NEW -> PREPARED
92
- payment.confirm_lock(amount=100) # PREPARED -> PRE_AUTH
93
- payment.confirm_payment(amount=100) # PRE_AUTH -> PARTIAL
94
- payment.mark_as_paid() # PARTIAL -> PAID (if fully paid)
89
+ from getpaid_core.fsm import apply_payment_update
90
+ from getpaid_core.types import PaymentUpdate
91
+
92
+ apply_payment_update(
93
+ payment,
94
+ PaymentUpdate(payment_event="prepared"),
95
+ )
96
+
97
+ apply_payment_update(
98
+ payment,
99
+ PaymentUpdate(
100
+ payment_event="payment_captured",
101
+ paid_amount=Decimal("100.00"),
102
+ ),
103
+ )
95
104
  ```
96
105
 
97
- See {doc}`concepts` for the full state diagram and transition rules.
106
+ See {doc}`concepts` for the lifecycle rules and semantic event mapping.
@@ -16,7 +16,7 @@
16
16
  :undoc-members:
17
17
  ```
18
18
 
19
- ## FSM
19
+ ## State Engine
20
20
 
21
21
  ```{eval-rst}
22
22
  .. automodule:: getpaid_core.fsm
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = 'python-getpaid-core'
3
- version = "0.1.1"
3
+ dynamic = ["version"]
4
4
  description = 'Framework-agnostic payment processing core.'
5
5
  readme = 'README.md'
6
6
  license = {text = 'MIT'}
@@ -52,6 +52,9 @@ build-backend = 'hatchling.build'
52
52
  [tool.hatch.build.targets.wheel]
53
53
  packages = ['src/getpaid_core']
54
54
 
55
+ [tool.hatch.version]
56
+ path = "src/getpaid_core/__init__.py"
57
+
55
58
  [tool.pytest.ini_options]
56
59
  testpaths = ['tests']
57
60
  asyncio_mode = 'auto'
@@ -1,10 +1,12 @@
1
1
  """Getpaid Core -- framework-agnostic payment processing."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "3.0.0a3"
4
4
 
5
5
  from getpaid_core.enums import BackendMethod
6
6
  from getpaid_core.enums import ConfirmationMethod
7
+ from getpaid_core.enums import FraudEvent
7
8
  from getpaid_core.enums import FraudStatus
9
+ from getpaid_core.enums import PaymentEvent
8
10
  from getpaid_core.enums import PaymentStatus
9
11
  from getpaid_core.exceptions import ChargeFailure
10
12
  from getpaid_core.exceptions import CommunicationError
@@ -16,24 +18,36 @@ from getpaid_core.exceptions import LockFailure
16
18
  from getpaid_core.exceptions import RefundFailure
17
19
  from getpaid_core.flow import PaymentFlow
18
20
  from getpaid_core.processor import BaseProcessor
21
+ from getpaid_core.registry import PluginRegistry
19
22
  from getpaid_core.registry import registry
23
+ from getpaid_core.types import ChargeResult
24
+ from getpaid_core.types import PaymentUpdate
25
+ from getpaid_core.types import RefundResult
26
+ from getpaid_core.types import TransactionResult
20
27
 
21
28
 
22
29
  __all__ = [
23
30
  "BackendMethod",
24
31
  "BaseProcessor",
25
32
  "ChargeFailure",
33
+ "ChargeResult",
26
34
  "CommunicationError",
27
35
  "ConfirmationMethod",
28
36
  "CredentialsError",
37
+ "FraudEvent",
29
38
  "FraudStatus",
30
39
  "GetPaidException",
31
40
  "InvalidCallbackError",
32
41
  "InvalidTransitionError",
33
42
  "LockFailure",
43
+ "PaymentEvent",
34
44
  "PaymentFlow",
35
45
  "PaymentStatus",
46
+ "PaymentUpdate",
47
+ "PluginRegistry",
36
48
  "RefundFailure",
49
+ "RefundResult",
50
+ "TransactionResult",
37
51
  "__version__",
38
52
  "registry",
39
53
  ]
@@ -0,0 +1,145 @@
1
+ """Dummy payment backend for development and testing."""
2
+
3
+ from collections.abc import Sequence
4
+ from decimal import Decimal
5
+ from typing import ClassVar
6
+
7
+ from getpaid_core.enums import BackendMethod
8
+ from getpaid_core.enums import FraudEvent
9
+ from getpaid_core.enums import PaymentEvent
10
+ from getpaid_core.processor import BaseProcessor
11
+ from getpaid_core.types import ChargeResult
12
+ from getpaid_core.types import PaymentUpdate
13
+ from getpaid_core.types import RefundResult
14
+ from getpaid_core.types import TransactionResult
15
+
16
+
17
+ class DummyProcessor(BaseProcessor):
18
+ """Dummy processor that simulates all payment operations."""
19
+
20
+ slug: ClassVar[str] = "dummy"
21
+ display_name: ClassVar[str] = "Dummy"
22
+ accepted_currencies: ClassVar[Sequence[str]] = (
23
+ "PLN",
24
+ "EUR",
25
+ "USD",
26
+ "GBP",
27
+ "CHF",
28
+ "CZK",
29
+ )
30
+
31
+ async def prepare_transaction(self, **kwargs) -> TransactionResult:
32
+ method = BackendMethod(self.get_setting("method", BackendMethod.REST))
33
+ if method is BackendMethod.POST:
34
+ return TransactionResult(
35
+ method=method,
36
+ redirect_url="https://dummy.example.com/form",
37
+ form_data={
38
+ "payment_id": self.payment.id,
39
+ "amount": f"{self.payment.amount_required:.2f}",
40
+ "currency": self.payment.currency,
41
+ },
42
+ )
43
+
44
+ return TransactionResult(
45
+ method=method,
46
+ redirect_url=f"https://dummy.example.com/pay/{self.payment.id}",
47
+ )
48
+
49
+ async def handle_callback(
50
+ self, data: dict, headers: dict, **kwargs
51
+ ) -> PaymentUpdate | None:
52
+ event = data.get("event")
53
+ if event == "payment_confirmed":
54
+ amount = Decimal(
55
+ str(data.get("paid_amount", self.payment.amount_required))
56
+ )
57
+ return PaymentUpdate(
58
+ payment_event=PaymentEvent.PAYMENT_CAPTURED,
59
+ paid_amount=amount,
60
+ provider_event_id=str(
61
+ data.get("event_id", f"payment:{self.payment.id}")
62
+ ),
63
+ )
64
+ if event == "payment_failed":
65
+ return PaymentUpdate(payment_event=PaymentEvent.FAILED)
66
+ if event == "payment_locked":
67
+ amount = Decimal(
68
+ str(data.get("locked_amount", self.payment.amount_required))
69
+ )
70
+ return PaymentUpdate(
71
+ payment_event=PaymentEvent.LOCKED,
72
+ locked_amount=amount,
73
+ )
74
+ if event == "refund_confirmed":
75
+ amount = Decimal(
76
+ str(data.get("refunded_amount", self.payment.amount_paid))
77
+ )
78
+ return PaymentUpdate(
79
+ payment_event=PaymentEvent.REFUND_CONFIRMED,
80
+ refunded_amount=amount,
81
+ provider_event_id=str(
82
+ data.get("event_id", f"refund:{self.payment.id}")
83
+ ),
84
+ )
85
+ if event == "refund_cancelled":
86
+ return PaymentUpdate(payment_event=PaymentEvent.REFUND_CANCELLED)
87
+ if event == "fraud_review":
88
+ return PaymentUpdate(
89
+ fraud_event=FraudEvent.REVIEW,
90
+ fraud_message="Manual review required",
91
+ )
92
+ if event == "fraud_rejected":
93
+ return PaymentUpdate(
94
+ fraud_event=FraudEvent.REJECT,
95
+ fraud_message="Rejected by dummy backend",
96
+ )
97
+ if event == "fraud_accepted":
98
+ return PaymentUpdate(
99
+ fraud_event=FraudEvent.ACCEPT,
100
+ fraud_message="Accepted by dummy backend",
101
+ )
102
+ return None
103
+
104
+ async def fetch_payment_status(self, **kwargs) -> PaymentUpdate | None:
105
+ event = self.get_setting("confirmation_event", "payment_confirmed")
106
+ if event == "payment_locked":
107
+ return PaymentUpdate(
108
+ payment_event=PaymentEvent.LOCKED,
109
+ locked_amount=self.payment.amount_required,
110
+ provider_event_id=f"pull-lock:{self.payment.id}",
111
+ )
112
+ if event == "payment_failed":
113
+ return PaymentUpdate(
114
+ payment_event=PaymentEvent.FAILED,
115
+ provider_event_id=f"pull-fail:{self.payment.id}",
116
+ )
117
+ return PaymentUpdate(
118
+ payment_event=PaymentEvent.PAYMENT_CAPTURED,
119
+ paid_amount=self.payment.amount_required,
120
+ provider_event_id=f"pull-pay:{self.payment.id}",
121
+ )
122
+
123
+ async def charge(
124
+ self, amount: Decimal | None = None, **kwargs
125
+ ) -> ChargeResult:
126
+ charged = amount if amount is not None else self.payment.amount_required
127
+ return ChargeResult(
128
+ amount_charged=charged,
129
+ success=True,
130
+ async_call=bool(kwargs.get("async_call", False)),
131
+ )
132
+
133
+ async def release_lock(self, **kwargs) -> Decimal:
134
+ return self.payment.amount_locked
135
+
136
+ async def start_refund(
137
+ self, amount: Decimal | None = None, **kwargs
138
+ ) -> RefundResult:
139
+ return RefundResult(
140
+ amount=amount if amount is not None else self.payment.amount_paid,
141
+ provider_data={"refund_id": f"dummy-refund-{self.payment.id}"},
142
+ )
143
+
144
+ async def cancel_refund(self, **kwargs) -> bool:
145
+ return True
@@ -1,7 +1,4 @@
1
- """Payment processing enums.
2
-
3
- Values are kept identical to django-getpaid for backward compatibility.
4
- """
1
+ """Payment processing enums."""
5
2
 
6
3
  from enum import StrEnum
7
4
 
@@ -29,6 +26,28 @@ class FraudStatus(StrEnum):
29
26
  CHECK = "check"
30
27
 
31
28
 
29
+ class PaymentEvent(StrEnum):
30
+ """Semantic payment lifecycle events reported by processors."""
31
+
32
+ PREPARED = "prepared"
33
+ LOCKED = "locked"
34
+ CHARGE_REQUESTED = "charge_requested"
35
+ PAYMENT_CAPTURED = "payment_captured"
36
+ FAILED = "failed"
37
+ REFUND_REQUESTED = "refund_requested"
38
+ REFUND_CONFIRMED = "refund_confirmed"
39
+ REFUND_CANCELLED = "refund_cancelled"
40
+ LOCK_RELEASED = "lock_released"
41
+
42
+
43
+ class FraudEvent(StrEnum):
44
+ """Semantic fraud review events reported by processors."""
45
+
46
+ REVIEW = "review"
47
+ ACCEPT = "accept"
48
+ REJECT = "reject"
49
+
50
+
32
51
  class BackendMethod(StrEnum):
33
52
  """HTTP method used to initiate payment."""
34
53