python-getpaid-core 0.1.0__tar.gz → 0.1.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.
- python_getpaid_core-0.1.1/.pre-commit-config.yaml +39 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/PKG-INFO +6 -4
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/README.md +4 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/docs/conf.py +1 -1
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/pyproject.toml +21 -7
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/flow.py +10 -6
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/fsm.py +20 -2
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/registry.py +13 -2
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/test_flow.py +15 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/test_fsm.py +16 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/test_registry.py +20 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/uv.lock +81 -356
- python_getpaid_core-0.1.0/.pre-commit-config.yaml +0 -14
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.cookiecutter.json +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.gitattributes +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.github/dependabot.yml +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.github/labels.yml +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.github/release-drafter.yml +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.github/workflows/constraints.txt +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.github/workflows/labeler.yml +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.github/workflows/release.yml +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.github/workflows/tests.yml +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.gitignore +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.plans/2026-02-13-getpaid-core-design.md +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.plans/2026-02-13-getpaid-core-implementation.md +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/.readthedocs.yml +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/CODE_OF_CONDUCT.md +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/CONTRIBUTING.md +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/LICENSE +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/codecov.yml +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/docs/changelog.md +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/docs/codeofconduct.md +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/docs/concepts.md +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/docs/contributing.md +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/docs/getting-started.md +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/docs/index.md +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/docs/license.md +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/docs/reference.md +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/docs/requirements.txt +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/__init__.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/backends/__init__.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/backends/dummy.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/enums.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/exceptions.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/processor.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/protocols.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/py.typed +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/types.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/src/getpaid_core/validators.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/__init__.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/conftest.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/test_dummy_backend.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/test_enums.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/test_exceptions.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/test_integration.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/test_processor.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/test_protocols.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/test_public_api.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/test_types.py +0 -0
- {python_getpaid_core-0.1.0 → python_getpaid_core-0.1.1}/tests/test_validators.py +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: local
|
|
3
|
+
hooks:
|
|
4
|
+
- id: ruff
|
|
5
|
+
name: ruff
|
|
6
|
+
entry: uv run ruff check --fix
|
|
7
|
+
language: system
|
|
8
|
+
types: [python]
|
|
9
|
+
- id: ruff-format
|
|
10
|
+
name: ruff-format
|
|
11
|
+
entry: uv run ruff format
|
|
12
|
+
language: system
|
|
13
|
+
types: [python]
|
|
14
|
+
- id: check-toml
|
|
15
|
+
name: check-toml
|
|
16
|
+
entry: uv run check-toml
|
|
17
|
+
language: system
|
|
18
|
+
types: [toml]
|
|
19
|
+
- id: check-yaml
|
|
20
|
+
name: check-yaml
|
|
21
|
+
entry: uv run check-yaml
|
|
22
|
+
language: system
|
|
23
|
+
types: [yaml]
|
|
24
|
+
- id: end-of-file-fixer
|
|
25
|
+
name: end-of-file-fixer
|
|
26
|
+
entry: uv run end-of-file-fixer
|
|
27
|
+
language: system
|
|
28
|
+
types: [text]
|
|
29
|
+
- id: trailing-whitespace
|
|
30
|
+
name: trailing-whitespace
|
|
31
|
+
entry: uv run trailing-whitespace-fixer
|
|
32
|
+
language: system
|
|
33
|
+
types: [text]
|
|
34
|
+
- id: ty
|
|
35
|
+
name: ty
|
|
36
|
+
entry: uv run ty check
|
|
37
|
+
language: system
|
|
38
|
+
types: [python]
|
|
39
|
+
pass_filenames: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-getpaid-core
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Framework-agnostic payment processing core.
|
|
5
5
|
Project-URL: Homepage, https://github.com/django-getpaid/python-getpaid-core
|
|
6
6
|
Project-URL: Repository, https://github.com/django-getpaid/python-getpaid-core
|
|
@@ -12,14 +12,12 @@ License-File: LICENSE
|
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
17
|
Classifier: Topic :: Office/Business :: Financial
|
|
20
18
|
Classifier: Topic :: Office/Business :: Financial :: Point-Of-Sale
|
|
21
19
|
Classifier: Typing :: Typed
|
|
22
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.12
|
|
23
21
|
Requires-Dist: anyio>=4.0
|
|
24
22
|
Requires-Dist: httpx>=0.27.0
|
|
25
23
|
Requires-Dist: transitions>=0.9.0
|
|
@@ -57,6 +55,10 @@ any web framework:
|
|
|
57
55
|
|
|
58
56
|
- **[django-getpaid](https://github.com/django-getpaid/django-getpaid)** —
|
|
59
57
|
Django adapter (models, views, forms, admin)
|
|
58
|
+
- **[fastapi-getpaid](https://github.com/django-getpaid/fastapi-getpaid)** —
|
|
59
|
+
FastAPI adapter (async routes, SQLAlchemy, Pydantic config)
|
|
60
|
+
- **[litestar-getpaid](https://github.com/django-getpaid/litestar-getpaid)** —
|
|
61
|
+
Litestar adapter (controllers, Provide DI, SQLAlchemy, Pydantic config)
|
|
60
62
|
|
|
61
63
|
## Installation
|
|
62
64
|
|
|
@@ -30,6 +30,10 @@ any web framework:
|
|
|
30
30
|
|
|
31
31
|
- **[django-getpaid](https://github.com/django-getpaid/django-getpaid)** —
|
|
32
32
|
Django adapter (models, views, forms, admin)
|
|
33
|
+
- **[fastapi-getpaid](https://github.com/django-getpaid/fastapi-getpaid)** —
|
|
34
|
+
FastAPI adapter (async routes, SQLAlchemy, Pydantic config)
|
|
35
|
+
- **[litestar-getpaid](https://github.com/django-getpaid/litestar-getpaid)** —
|
|
36
|
+
Litestar adapter (controllers, Provide DI, SQLAlchemy, Pydantic config)
|
|
33
37
|
|
|
34
38
|
## Installation
|
|
35
39
|
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = 'python-getpaid-core'
|
|
3
|
-
version =
|
|
3
|
+
version = "0.1.1"
|
|
4
4
|
description = 'Framework-agnostic payment processing core.'
|
|
5
5
|
readme = 'README.md'
|
|
6
6
|
license = {text = 'MIT'}
|
|
7
7
|
authors = [
|
|
8
8
|
{name = 'Dominik Kozaczko', email = 'dominik@kozaczko.info'},
|
|
9
9
|
]
|
|
10
|
-
requires-python = '>=3.
|
|
10
|
+
requires-python = '>=3.12'
|
|
11
11
|
classifiers = [
|
|
12
12
|
'Development Status :: 3 - Alpha',
|
|
13
13
|
'Intended Audience :: Developers',
|
|
14
14
|
'License :: OSI Approved :: MIT License',
|
|
15
|
-
'Programming Language :: Python :: 3.10',
|
|
16
|
-
'Programming Language :: Python :: 3.11',
|
|
17
15
|
'Programming Language :: Python :: 3.12',
|
|
18
16
|
'Programming Language :: Python :: 3.13',
|
|
19
17
|
'Topic :: Office/Business :: Financial',
|
|
@@ -32,11 +30,13 @@ dev = [
|
|
|
32
30
|
'pytest-asyncio>=0.24.0',
|
|
33
31
|
'pytest-cov>=5.0',
|
|
34
32
|
'respx>=0.22.0',
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
"ruff>=0.9.0",
|
|
34
|
+
"pre-commit>=4.0",
|
|
37
35
|
"furo>=2025.12.19",
|
|
38
36
|
"sphinx>=8.1.3",
|
|
39
37
|
"myst-parser>=4.0.1",
|
|
38
|
+
"pre-commit-hooks>=6.0.0",
|
|
39
|
+
"ty>=0.0.16",
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
[project.urls]
|
|
@@ -64,7 +64,7 @@ source = ['getpaid_core']
|
|
|
64
64
|
show_missing = true
|
|
65
65
|
|
|
66
66
|
[tool.ruff]
|
|
67
|
-
target-version = '
|
|
67
|
+
target-version = 'py312'
|
|
68
68
|
line-length = 80
|
|
69
69
|
src = ['src', 'tests']
|
|
70
70
|
|
|
@@ -94,3 +94,17 @@ ignore = [
|
|
|
94
94
|
force-single-line = true
|
|
95
95
|
lines-after-imports = 2
|
|
96
96
|
known-first-party = ['getpaid_core']
|
|
97
|
+
|
|
98
|
+
# --- ty type checker ---
|
|
99
|
+
[tool.ty.environment]
|
|
100
|
+
python-version = '3.12'
|
|
101
|
+
|
|
102
|
+
[tool.ty.terminal]
|
|
103
|
+
error-on-warning = true
|
|
104
|
+
|
|
105
|
+
# Relax rules for test files: FSM trigger methods are dynamically attached
|
|
106
|
+
[[tool.ty.overrides]]
|
|
107
|
+
include = ['tests/**']
|
|
108
|
+
[tool.ty.overrides.rules]
|
|
109
|
+
unresolved-attribute = 'ignore'
|
|
110
|
+
invalid-argument-type = 'ignore'
|
|
@@ -58,7 +58,7 @@ class PaymentFlow:
|
|
|
58
58
|
create_payment_machine(payment)
|
|
59
59
|
processor = self._get_processor(payment)
|
|
60
60
|
result = await processor.prepare_transaction(**kwargs)
|
|
61
|
-
payment.confirm_prepared()
|
|
61
|
+
payment.confirm_prepared() # ty: ignore[unresolved-attribute] # FSM trigger attached by create_payment_machine
|
|
62
62
|
await self.repository.save(payment)
|
|
63
63
|
return result
|
|
64
64
|
|
|
@@ -91,7 +91,11 @@ class PaymentFlow:
|
|
|
91
91
|
)
|
|
92
92
|
trigger = getattr(payment, callback, None)
|
|
93
93
|
if trigger and callable(trigger):
|
|
94
|
-
|
|
94
|
+
trigger_kwargs = {}
|
|
95
|
+
amount = response.get("amount")
|
|
96
|
+
if amount is not None:
|
|
97
|
+
trigger_kwargs["amount"] = amount
|
|
98
|
+
trigger(**trigger_kwargs)
|
|
95
99
|
await self.repository.save(payment)
|
|
96
100
|
return payment
|
|
97
101
|
|
|
@@ -101,7 +105,7 @@ class PaymentFlow:
|
|
|
101
105
|
create_payment_machine(payment)
|
|
102
106
|
result = await processor.charge(amount=amount, **kwargs)
|
|
103
107
|
if result["success"]:
|
|
104
|
-
payment.confirm_charge_sent()
|
|
108
|
+
payment.confirm_charge_sent() # ty: ignore[unresolved-attribute] # FSM trigger attached by create_payment_machine
|
|
105
109
|
await self.repository.save(payment)
|
|
106
110
|
return result
|
|
107
111
|
|
|
@@ -110,7 +114,7 @@ class PaymentFlow:
|
|
|
110
114
|
processor = self._get_processor(payment)
|
|
111
115
|
create_payment_machine(payment)
|
|
112
116
|
amount = await processor.release_lock(**kwargs)
|
|
113
|
-
payment.release_lock()
|
|
117
|
+
payment.release_lock() # ty: ignore[unresolved-attribute] # FSM trigger attached by create_payment_machine
|
|
114
118
|
await self.repository.save(payment)
|
|
115
119
|
return amount
|
|
116
120
|
|
|
@@ -119,7 +123,7 @@ class PaymentFlow:
|
|
|
119
123
|
processor = self._get_processor(payment)
|
|
120
124
|
create_payment_machine(payment)
|
|
121
125
|
refund_amount = await processor.start_refund(amount=amount, **kwargs)
|
|
122
|
-
payment.start_refund()
|
|
126
|
+
payment.start_refund() # ty: ignore[unresolved-attribute] # FSM trigger attached by create_payment_machine
|
|
123
127
|
await self.repository.save(payment)
|
|
124
128
|
return refund_amount
|
|
125
129
|
|
|
@@ -129,7 +133,7 @@ class PaymentFlow:
|
|
|
129
133
|
create_payment_machine(payment)
|
|
130
134
|
success = await processor.cancel_refund(**kwargs)
|
|
131
135
|
if success:
|
|
132
|
-
payment.cancel_refund()
|
|
136
|
+
payment.cancel_refund() # ty: ignore[unresolved-attribute] # FSM trigger attached by create_payment_machine
|
|
133
137
|
await self.repository.save(payment)
|
|
134
138
|
return success
|
|
135
139
|
|
|
@@ -49,6 +49,15 @@ def _accumulate_paid_amount(event_data):
|
|
|
49
49
|
model.amount_paid += amount
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
def _accumulate_refunded_amount(event_data):
|
|
53
|
+
"""After confirm_refund: accumulate refunded amount on the payment."""
|
|
54
|
+
model = event_data.model
|
|
55
|
+
amount = event_data.kwargs.get("amount", None)
|
|
56
|
+
if amount is None:
|
|
57
|
+
amount = model.amount_paid - model.amount_refunded
|
|
58
|
+
model.amount_refunded += amount
|
|
59
|
+
|
|
60
|
+
|
|
52
61
|
PAYMENT_TRANSITIONS = [
|
|
53
62
|
{
|
|
54
63
|
"trigger": "confirm_prepared",
|
|
@@ -105,6 +114,7 @@ PAYMENT_TRANSITIONS = [
|
|
|
105
114
|
"trigger": "confirm_refund",
|
|
106
115
|
"source": PaymentStatus.REFUND_STARTED,
|
|
107
116
|
"dest": PaymentStatus.PARTIAL,
|
|
117
|
+
"after": _accumulate_refunded_amount,
|
|
108
118
|
},
|
|
109
119
|
{
|
|
110
120
|
"trigger": "mark_as_refunded",
|
|
@@ -174,11 +184,14 @@ def create_payment_machine(payment) -> Machine:
|
|
|
174
184
|
The transitions library adds trigger methods directly to the
|
|
175
185
|
object (confirm_prepared, confirm_lock, fail, etc.).
|
|
176
186
|
"""
|
|
187
|
+
initial = (
|
|
188
|
+
PaymentStatus(payment.status) if payment.status else PaymentStatus.NEW
|
|
189
|
+
)
|
|
177
190
|
return Machine(
|
|
178
191
|
model=payment,
|
|
179
192
|
states=PaymentStatus,
|
|
180
193
|
transitions=PAYMENT_TRANSITIONS,
|
|
181
|
-
initial=
|
|
194
|
+
initial=initial,
|
|
182
195
|
model_attribute="status",
|
|
183
196
|
auto_transitions=False,
|
|
184
197
|
send_event=True,
|
|
@@ -193,11 +206,16 @@ def _store_fraud_message(event):
|
|
|
193
206
|
|
|
194
207
|
def create_fraud_machine(payment) -> Machine:
|
|
195
208
|
"""Attach fraud status FSM to a payment object."""
|
|
209
|
+
initial = (
|
|
210
|
+
FraudStatus(payment.fraud_status)
|
|
211
|
+
if payment.fraud_status
|
|
212
|
+
else FraudStatus.UNKNOWN
|
|
213
|
+
)
|
|
196
214
|
return Machine(
|
|
197
215
|
model=payment,
|
|
198
216
|
states=FraudStatus,
|
|
199
217
|
transitions=FRAUD_TRANSITIONS,
|
|
200
|
-
initial=
|
|
218
|
+
initial=initial,
|
|
201
219
|
model_attribute="fraud_status",
|
|
202
220
|
auto_transitions=False,
|
|
203
221
|
send_event=True,
|
|
@@ -27,12 +27,12 @@ class PluginRegistry:
|
|
|
27
27
|
if isinstance(processor_class, type) and issubclass(
|
|
28
28
|
processor_class, BaseProcessor
|
|
29
29
|
):
|
|
30
|
-
self.
|
|
30
|
+
self._register_backend(processor_class)
|
|
31
31
|
self._discovered = True
|
|
32
32
|
|
|
33
33
|
def register(self, processor_class: type[BaseProcessor]) -> None:
|
|
34
34
|
"""Manual registration for testing or dynamic use."""
|
|
35
|
-
self.
|
|
35
|
+
self._register_backend(processor_class)
|
|
36
36
|
|
|
37
37
|
def unregister(self, slug: str) -> None:
|
|
38
38
|
"""Remove a backend by slug."""
|
|
@@ -70,5 +70,16 @@ class PluginRegistry:
|
|
|
70
70
|
if not self._discovered:
|
|
71
71
|
self.discover()
|
|
72
72
|
|
|
73
|
+
def _register_backend(self, processor_class: type[BaseProcessor]) -> None:
|
|
74
|
+
slug = processor_class.slug
|
|
75
|
+
existing = self._backends.get(slug)
|
|
76
|
+
if existing is not None and existing is not processor_class:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"Duplicate backend slug {slug!r}: "
|
|
79
|
+
f"{existing.__module__}.{existing.__name__} and "
|
|
80
|
+
f"{processor_class.__module__}.{processor_class.__name__}"
|
|
81
|
+
)
|
|
82
|
+
self._backends[slug] = processor_class
|
|
83
|
+
|
|
73
84
|
|
|
74
85
|
registry = PluginRegistry()
|
|
@@ -109,6 +109,21 @@ class TestFetchAndUpdateStatus:
|
|
|
109
109
|
):
|
|
110
110
|
await flow.fetch_and_update_status(payment)
|
|
111
111
|
|
|
112
|
+
@pytest.mark.asyncio
|
|
113
|
+
async def test_pull_passes_amount_to_transition(self, flow):
|
|
114
|
+
payment = MockPayment(backend="mock", status=PaymentStatus.PREPARED)
|
|
115
|
+
with patch.object(
|
|
116
|
+
MockProcessor,
|
|
117
|
+
"fetch_payment_status",
|
|
118
|
+
new_callable=AsyncMock,
|
|
119
|
+
return_value={
|
|
120
|
+
"status": "confirm_payment",
|
|
121
|
+
"amount": Decimal("40.00"),
|
|
122
|
+
},
|
|
123
|
+
):
|
|
124
|
+
result = await flow.fetch_and_update_status(payment)
|
|
125
|
+
assert result.amount_paid == Decimal("40.00")
|
|
126
|
+
|
|
112
127
|
|
|
113
128
|
class TestCharge:
|
|
114
129
|
@pytest.mark.asyncio
|
|
@@ -144,6 +144,22 @@ class TestPaymentRefundFlow:
|
|
|
144
144
|
p.confirm_refund()
|
|
145
145
|
assert p.status == PaymentStatus.PARTIAL
|
|
146
146
|
|
|
147
|
+
def test_confirm_refund_accumulates_amount(self):
|
|
148
|
+
p = MockPayment(status=PaymentStatus.REFUND_STARTED)
|
|
149
|
+
p.amount_paid = 100
|
|
150
|
+
create_payment_machine(p)
|
|
151
|
+
p.confirm_refund(amount=30)
|
|
152
|
+
assert p.status == PaymentStatus.PARTIAL
|
|
153
|
+
assert p.amount_refunded == 30
|
|
154
|
+
|
|
155
|
+
def test_confirm_refund_defaults_to_remaining_amount(self):
|
|
156
|
+
p = MockPayment(status=PaymentStatus.REFUND_STARTED)
|
|
157
|
+
p.amount_paid = 100
|
|
158
|
+
p.amount_refunded = 40
|
|
159
|
+
create_payment_machine(p)
|
|
160
|
+
p.confirm_refund()
|
|
161
|
+
assert p.amount_refunded == 100
|
|
162
|
+
|
|
147
163
|
def test_mark_as_refunded_when_fully_refunded(self):
|
|
148
164
|
p = MockPayment(status=PaymentStatus.PARTIAL)
|
|
149
165
|
p.amount_paid = 100
|
|
@@ -55,6 +55,20 @@ class EURProcessor(BaseProcessor):
|
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
class DuplicatePLNProcessor(BaseProcessor):
|
|
59
|
+
slug = "pln-pay"
|
|
60
|
+
display_name = "Duplicate PLN"
|
|
61
|
+
accepted_currencies = ["PLN"]
|
|
62
|
+
|
|
63
|
+
async def prepare_transaction(self, **kwargs):
|
|
64
|
+
return TransactionResult(
|
|
65
|
+
redirect_url=None,
|
|
66
|
+
form_data=None,
|
|
67
|
+
method="REST",
|
|
68
|
+
headers={},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
58
72
|
# -- Tests --
|
|
59
73
|
|
|
60
74
|
|
|
@@ -71,6 +85,12 @@ class TestManualRegistration:
|
|
|
71
85
|
assert reg.get_by_slug("pln-pay") is PLNProcessor
|
|
72
86
|
assert reg.get_by_slug("eur-pay") is EURProcessor
|
|
73
87
|
|
|
88
|
+
def test_register_duplicate_slug_raises(self):
|
|
89
|
+
reg = PluginRegistry()
|
|
90
|
+
reg.register(PLNProcessor)
|
|
91
|
+
with pytest.raises(ValueError, match="Duplicate backend slug"):
|
|
92
|
+
reg.register(DuplicatePLNProcessor)
|
|
93
|
+
|
|
74
94
|
def test_unregister(self):
|
|
75
95
|
reg = PluginRegistry()
|
|
76
96
|
reg.register(PLNProcessor)
|