pywayforpay 0.2.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.
- pywayforpay-0.2.0/.github/workflows/ci.yml +99 -0
- pywayforpay-0.2.0/.github/workflows/publish.yml +41 -0
- pywayforpay-0.2.0/.github/workflows/release.yml +56 -0
- pywayforpay-0.2.0/.gitignore +16 -0
- pywayforpay-0.2.0/.pre-commit-config.yaml +6 -0
- pywayforpay-0.2.0/CHANGELOG.md +9 -0
- pywayforpay-0.2.0/LICENSE +21 -0
- pywayforpay-0.2.0/PKG-INFO +194 -0
- pywayforpay-0.2.0/README.md +169 -0
- pywayforpay-0.2.0/docs/antifraud.md +35 -0
- pywayforpay-0.2.0/docs/api/clients.md +17 -0
- pywayforpay-0.2.0/docs/api/data.md +23 -0
- pywayforpay-0.2.0/docs/api/enums.md +25 -0
- pywayforpay-0.2.0/docs/api/requests.md +37 -0
- pywayforpay-0.2.0/docs/api/responses.md +22 -0
- pywayforpay-0.2.0/docs/api/webhooks.md +7 -0
- pywayforpay-0.2.0/docs/errors.md +57 -0
- pywayforpay-0.2.0/docs/index.md +87 -0
- pywayforpay-0.2.0/docs/mms.md +31 -0
- pywayforpay-0.2.0/docs/payment-flows.md +119 -0
- pywayforpay-0.2.0/docs/pci-dss.md +43 -0
- pywayforpay-0.2.0/docs/quickstart.md +82 -0
- pywayforpay-0.2.0/docs/recurring.md +61 -0
- pywayforpay-0.2.0/docs/signing.md +38 -0
- pywayforpay-0.2.0/docs/webhooks.md +62 -0
- pywayforpay-0.2.0/docs/widget.md +63 -0
- pywayforpay-0.2.0/examples/async_api.py +50 -0
- pywayforpay-0.2.0/examples/charge.py +49 -0
- pywayforpay-0.2.0/examples/purchase_form.py +43 -0
- pywayforpay-0.2.0/examples/verify_form.py +33 -0
- pywayforpay-0.2.0/examples/webhooks.py +55 -0
- pywayforpay-0.2.0/examples/widget.py +39 -0
- pywayforpay-0.2.0/mkdocs.yml +52 -0
- pywayforpay-0.2.0/pyproject.toml +96 -0
- pywayforpay-0.2.0/scripts/apply-protection.sh +69 -0
- pywayforpay-0.2.0/src/wayforpay/__init__.py +31 -0
- pywayforpay-0.2.0/src/wayforpay/_async_client.py +486 -0
- pywayforpay-0.2.0/src/wayforpay/_constants.py +22 -0
- pywayforpay-0.2.0/src/wayforpay/_encoding.py +56 -0
- pywayforpay-0.2.0/src/wayforpay/_errors.py +18 -0
- pywayforpay-0.2.0/src/wayforpay/_money.py +7 -0
- pywayforpay-0.2.0/src/wayforpay/_signature.py +49 -0
- pywayforpay-0.2.0/src/wayforpay/_transports.py +76 -0
- pywayforpay-0.2.0/src/wayforpay/client.py +509 -0
- pywayforpay-0.2.0/src/wayforpay/models/__init__.py +179 -0
- pywayforpay-0.2.0/src/wayforpay/models/_base.py +17 -0
- pywayforpay-0.2.0/src/wayforpay/models/antifraud.py +124 -0
- pywayforpay-0.2.0/src/wayforpay/models/data.py +108 -0
- pywayforpay-0.2.0/src/wayforpay/models/enums.py +365 -0
- pywayforpay-0.2.0/src/wayforpay/models/requests.py +737 -0
- pywayforpay-0.2.0/src/wayforpay/models/responses.py +198 -0
- pywayforpay-0.2.0/src/wayforpay/py.typed +0 -0
- pywayforpay-0.2.0/src/wayforpay/webhooks.py +103 -0
- pywayforpay-0.2.0/tests/test_client_async.py +88 -0
- pywayforpay-0.2.0/tests/test_client_sync.py +315 -0
- pywayforpay-0.2.0/tests/test_models.py +144 -0
- pywayforpay-0.2.0/tests/test_signature.py +52 -0
- pywayforpay-0.2.0/tests/test_webhooks.py +127 -0
- pywayforpay-0.2.0/uv.lock +1288 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
commit-lint:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
with:
|
|
14
|
+
fetch-depth: 0
|
|
15
|
+
|
|
16
|
+
- name: Install uv
|
|
17
|
+
uses: astral-sh/setup-uv@v5
|
|
18
|
+
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
|
|
24
|
+
- name: Install project
|
|
25
|
+
run: uv sync
|
|
26
|
+
|
|
27
|
+
- name: Check conventional commits
|
|
28
|
+
run: |
|
|
29
|
+
if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
|
|
30
|
+
uv run cz check --rev-range "${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
|
|
31
|
+
uv run cz check -m "${{ github.event.pull_request.title }}"
|
|
32
|
+
elif [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then
|
|
33
|
+
uv run cz check --rev-range "${{ github.event.before }}..HEAD"
|
|
34
|
+
else
|
|
35
|
+
uv run cz check --rev-range HEAD~1..HEAD
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
lint-type-test:
|
|
39
|
+
strategy:
|
|
40
|
+
matrix:
|
|
41
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v4
|
|
45
|
+
|
|
46
|
+
- name: Install uv
|
|
47
|
+
uses: astral-sh/setup-uv@v5
|
|
48
|
+
|
|
49
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
50
|
+
uses: actions/setup-python@v5
|
|
51
|
+
with:
|
|
52
|
+
python-version: ${{ matrix.python-version }}
|
|
53
|
+
|
|
54
|
+
- name: Install project
|
|
55
|
+
run: uv sync --python ${{ matrix.python-version }}
|
|
56
|
+
|
|
57
|
+
- name: Lint
|
|
58
|
+
run: uv run ruff check .
|
|
59
|
+
|
|
60
|
+
- name: Type check
|
|
61
|
+
run: uv run basedpyright
|
|
62
|
+
|
|
63
|
+
- name: Test
|
|
64
|
+
run: uv run pytest
|
|
65
|
+
|
|
66
|
+
- name: Upload coverage reports to Codecov
|
|
67
|
+
uses: codecov/codecov-action@v5
|
|
68
|
+
with:
|
|
69
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
70
|
+
files: coverage.xml
|
|
71
|
+
fail_ci_if_error: false
|
|
72
|
+
|
|
73
|
+
docs:
|
|
74
|
+
runs-on: ubuntu-latest
|
|
75
|
+
permissions:
|
|
76
|
+
contents: write
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/checkout@v4
|
|
79
|
+
|
|
80
|
+
- name: Install uv
|
|
81
|
+
uses: astral-sh/setup-uv@v5
|
|
82
|
+
|
|
83
|
+
- name: Set up Python
|
|
84
|
+
uses: actions/setup-python@v5
|
|
85
|
+
with:
|
|
86
|
+
python-version: "3.12"
|
|
87
|
+
|
|
88
|
+
- name: Install project
|
|
89
|
+
run: uv sync
|
|
90
|
+
|
|
91
|
+
- name: Build docs
|
|
92
|
+
run: uv run mkdocs build --strict
|
|
93
|
+
|
|
94
|
+
- name: Deploy to GitHub Pages
|
|
95
|
+
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
|
96
|
+
uses: peaceiris/actions-gh-pages@v4
|
|
97
|
+
with:
|
|
98
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
99
|
+
publish_dir: ./site
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
repository:
|
|
9
|
+
description: PyPI repository to publish to
|
|
10
|
+
type: choice
|
|
11
|
+
default: testpypi
|
|
12
|
+
options:
|
|
13
|
+
- testpypi
|
|
14
|
+
- pypi
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
publish:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
environment: release
|
|
20
|
+
permissions:
|
|
21
|
+
id-token: write
|
|
22
|
+
env:
|
|
23
|
+
PYPI_REPOSITORY_URL: ${{ github.event.inputs.repository == 'testpypi' && 'https://test.pypi.org/legacy/' || 'https://upload.pypi.org/legacy/' }}
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Install uv
|
|
28
|
+
uses: astral-sh/setup-uv@v5
|
|
29
|
+
|
|
30
|
+
- name: Set up Python
|
|
31
|
+
uses: actions/setup-python@v5
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.12"
|
|
34
|
+
|
|
35
|
+
- name: Build distributions
|
|
36
|
+
run: uv build
|
|
37
|
+
|
|
38
|
+
- name: Publish package distributions to PyPI
|
|
39
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
40
|
+
with:
|
|
41
|
+
packages-dir: dist
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
release:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: write
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
with:
|
|
15
|
+
fetch-depth: 0
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v5
|
|
19
|
+
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
|
|
25
|
+
- name: Install project
|
|
26
|
+
run: uv sync
|
|
27
|
+
|
|
28
|
+
- name: Generate changelog
|
|
29
|
+
run: uv run cz changelog
|
|
30
|
+
|
|
31
|
+
- name: Extract changelog section for this tag
|
|
32
|
+
run: |
|
|
33
|
+
python - "${{ github.ref_name }}" <<'PY'
|
|
34
|
+
import sys
|
|
35
|
+
tag = sys.argv[1]
|
|
36
|
+
lines = open("CHANGELOG.md").read().splitlines()
|
|
37
|
+
start = next(i for i, l in enumerate(lines) if l.startswith("## ") and tag in l)
|
|
38
|
+
end = next(
|
|
39
|
+
(i for i in range(start + 1, len(lines)) if lines[i].startswith("## ")),
|
|
40
|
+
len(lines),
|
|
41
|
+
)
|
|
42
|
+
open("/tmp/release-notes.md", "w").write("\n".join(lines[start:end]) + "\n")
|
|
43
|
+
PY
|
|
44
|
+
|
|
45
|
+
- name: Commit changelog back to master
|
|
46
|
+
run: |
|
|
47
|
+
git config user.name "github-actions[bot]"
|
|
48
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
49
|
+
git add CHANGELOG.md
|
|
50
|
+
git commit -m "docs: update CHANGELOG.md for ${{ github.ref_name }}" || true
|
|
51
|
+
git push origin HEAD:master || true
|
|
52
|
+
|
|
53
|
+
- name: Create GitHub release
|
|
54
|
+
uses: softprops/action-gh-release@v2
|
|
55
|
+
with:
|
|
56
|
+
body_path: /tmp/release-notes.md
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Filip Shramko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pywayforpay
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Strongly typed WayForPay PSP API SDK (msgspec-based, sync + async)
|
|
5
|
+
Project-URL: Documentation, https://wiki.wayforpay.com/en/
|
|
6
|
+
Project-URL: Repository, https://github.com/HermanPlay/pywayforpay
|
|
7
|
+
Author: Filip Shramko
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: api,msgspec,payment-gateway,payments,psp,sdk,wayforpay
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: httpx>=0.27
|
|
23
|
+
Requires-Dist: msgspec>=0.18
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# pywayforpay
|
|
27
|
+
|
|
28
|
+
[](https://github.com/HermanPlay/pywayforpay/actions/workflows/ci.yml)
|
|
29
|
+
[](https://codecov.io/gh/HermanPlay/pywayforpay)
|
|
30
|
+
[](https://pypi.org/project/pywayforpay/)
|
|
31
|
+
[](https://HermanPlay.github.io/pywayforpay/)
|
|
32
|
+
|
|
33
|
+
Strongly typed [WayForPay](https://wiki.wayforpay.com/en/) PSP API SDK for Python (3.11+),
|
|
34
|
+
built on [msgspec](https://msgspec.dev/) with explicit structs, HMAC-MD5 signature handling,
|
|
35
|
+
and sync + async [httpx](https://www.python-httpx.org/) clients.
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Payments** — purchase (redirect form, widget, offline link), verify, charge (host-to-host),
|
|
40
|
+
3-DS completion, settle, refund, invoices, QR, P2P transfers, currency rates.
|
|
41
|
+
- **Recurring payments** — status, suspend, resume, remove, change.
|
|
42
|
+
- **Reporting** — check status, transaction list.
|
|
43
|
+
- **Merchant management (MMS)** — merchants, partners, balances.
|
|
44
|
+
- **Antifraud** — validate and check status.
|
|
45
|
+
- **Webhooks** — verify callbacks and acks, build acks.
|
|
46
|
+
- Fully typed (`py.typed`), documented, sync + async APIs in lockstep.
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install pywayforpay
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Requires Python 3.11+.
|
|
55
|
+
|
|
56
|
+
## Quickstart
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from wayforpay import Client, Credentials, money
|
|
60
|
+
from wayforpay.models import PurchaseRequest, Product, Currency
|
|
61
|
+
|
|
62
|
+
creds = Credentials(
|
|
63
|
+
merchant_account="test_merch_n1",
|
|
64
|
+
merchant_secret_key="flk3409refn54t54t*FNJRET",
|
|
65
|
+
merchant_domain_name="www.market.ua",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
product = Product(name="Dell XPS", price=money("1547.36"), count=1)
|
|
69
|
+
request = PurchaseRequest.from_products(
|
|
70
|
+
order_reference="DH783023",
|
|
71
|
+
order_date=1415379863,
|
|
72
|
+
amount=money("1547.36"),
|
|
73
|
+
currency=Currency.UAH,
|
|
74
|
+
products=[product],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
with Client(creds) as client:
|
|
78
|
+
url = client.purchase_url() # https://secure.wayforpay.com/pay
|
|
79
|
+
form = client.purchase_form(request) # signed POST payload for the payment page
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Render `form` as hidden fields inside `<form action="{url}" method="POST">`.
|
|
83
|
+
|
|
84
|
+
Async mirrors sync:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import asyncio
|
|
88
|
+
from wayforpay import AsyncClient, Credentials
|
|
89
|
+
|
|
90
|
+
async def main() -> None:
|
|
91
|
+
async with AsyncClient(creds) as client:
|
|
92
|
+
result = await client.check_status(CheckStatusRequest(order_reference="DH783023"))
|
|
93
|
+
|
|
94
|
+
asyncio.run(main())
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Examples
|
|
98
|
+
|
|
99
|
+
Runnable examples live in [`examples/`](examples/):
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
uv run python examples/purchase_form.py # signed purchase form + HTML snippet
|
|
103
|
+
uv run python examples/widget.py # payment-widget config
|
|
104
|
+
uv run python examples/webhooks.py # callback verification + ack
|
|
105
|
+
uv run python examples/verify_form.py # card verification form
|
|
106
|
+
uv run python examples/async_api.py # async client (dry-run; add --live)
|
|
107
|
+
uv run python examples/charge.py # host-to-host charge (dry-run; add --live)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Documentation
|
|
111
|
+
|
|
112
|
+
Full documentation is available at <https://HermanPlay.github.io/pywayforpay/>.
|
|
113
|
+
|
|
114
|
+
- [Quickstart](https://HermanPlay.github.io/pywayforpay/quickstart/)
|
|
115
|
+
- [Payments](https://HermanPlay.github.io/pywayforpay/payment-flows/)
|
|
116
|
+
- [Payment widget](https://HermanPlay.github.io/pywayforpay/widget/)
|
|
117
|
+
- [Webhooks](https://HermanPlay.github.io/pywayforpay/webhooks/)
|
|
118
|
+
- [Recurring payments](https://HermanPlay.github.io/pywayforpay/recurring/)
|
|
119
|
+
- [Merchant management (MMS)](https://HermanPlay.github.io/pywayforpay/mms/)
|
|
120
|
+
- [Antifraud](https://HermanPlay.github.io/pywayforpay/antifraud/)
|
|
121
|
+
- [Errors & reason codes](https://HermanPlay.github.io/pywayforpay/errors/)
|
|
122
|
+
- [PCI DSS](https://HermanPlay.github.io/pywayforpay/pci-dss/)
|
|
123
|
+
- [API reference](https://HermanPlay.github.io/pywayforpay/api/clients/)
|
|
124
|
+
|
|
125
|
+
WayForPay API docs: <https://wiki.wayforpay.com/en/>
|
|
126
|
+
|
|
127
|
+
## PCI DSS
|
|
128
|
+
|
|
129
|
+
This library exposes host-to-host card operations (`ChargeRequest`, `CardData`).
|
|
130
|
+
Handling raw card data carries PCI DSS obligations; the merchant/integrator remains
|
|
131
|
+
responsible for compliance. Prefer the hosted payment widget or redirect form to avoid
|
|
132
|
+
touching card data. See the [PCI DSS guide](https://HermanPlay.github.io/pywayforpay/pci-dss/).
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
Install dev deps: `uv sync`.
|
|
137
|
+
|
|
138
|
+
### Conventional commits
|
|
139
|
+
|
|
140
|
+
Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/)
|
|
141
|
+
(`feat:`, `fix:`, `docs:`, `refactor:`, `ci:`, ...). The `commit-lint` CI job checks
|
|
142
|
+
every commit in a PR (and the PR title) with commitizen and fails on violations.
|
|
143
|
+
|
|
144
|
+
The `master` branch is protected by a GitHub ruleset (`scripts/apply-protection.sh`):
|
|
145
|
+
|
|
146
|
+
- direct pushes and force-pushes to `master` are rejected — all changes go through PRs;
|
|
147
|
+
- a PR needs one approving review and a green `commit-lint` check before merge.
|
|
148
|
+
|
|
149
|
+
So the remote rejects non-conventional commits even if a contributor skips local hooks.
|
|
150
|
+
The pre-commit hook is convenience only:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
uv run pre-commit install --hook-type commit-msg # one-time local setup
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Releases
|
|
157
|
+
|
|
158
|
+
Publishing creates a version tag, a GitHub release with a changelog generated from
|
|
159
|
+
the commit log, and a PyPI package published via trusted publishing (OIDC).
|
|
160
|
+
|
|
161
|
+
1. Bump the version and tag it:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
uv run cz bump
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Bumps `pyproject.toml`, `__init__.py` and `uv.lock`, updates `CHANGELOG.md`, and
|
|
168
|
+
creates a `vX.Y.Z` tag. Add `--yes --increment patch|minor|major` to skip the
|
|
169
|
+
interactive prompt (e.g. in scripts/CI).
|
|
170
|
+
|
|
171
|
+
2. Push the tag:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
git push origin master --tags
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The `release` workflow builds the changelog for the tag and creates a GitHub
|
|
178
|
+
release. The `publish` workflow then publishes the package to PyPI.
|
|
179
|
+
|
|
180
|
+
#### One-time PyPI setup (trusted publishing)
|
|
181
|
+
|
|
182
|
+
The `publish` workflow uses PyPI trusted publishing (OIDC) — no API tokens stored.
|
|
183
|
+
|
|
184
|
+
- Enable 2FA on your PyPI and TestPyPI accounts (PyPI policy requirement).
|
|
185
|
+
- Register the project `pywayforpay` on PyPI (and TestPyPI).
|
|
186
|
+
- Add a trusted publisher on each: owner `HermanPlay`, repository `pywayforpay`,
|
|
187
|
+
workflow file `publish.yml`, environment `release`.
|
|
188
|
+
|
|
189
|
+
To validate a release against TestPyPI first, trigger the `publish` workflow manually
|
|
190
|
+
with repository `testpypi` (Actions tab → Publish to PyPI → Run workflow).
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# pywayforpay
|
|
2
|
+
|
|
3
|
+
[](https://github.com/HermanPlay/pywayforpay/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codecov.io/gh/HermanPlay/pywayforpay)
|
|
5
|
+
[](https://pypi.org/project/pywayforpay/)
|
|
6
|
+
[](https://HermanPlay.github.io/pywayforpay/)
|
|
7
|
+
|
|
8
|
+
Strongly typed [WayForPay](https://wiki.wayforpay.com/en/) PSP API SDK for Python (3.11+),
|
|
9
|
+
built on [msgspec](https://msgspec.dev/) with explicit structs, HMAC-MD5 signature handling,
|
|
10
|
+
and sync + async [httpx](https://www.python-httpx.org/) clients.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Payments** — purchase (redirect form, widget, offline link), verify, charge (host-to-host),
|
|
15
|
+
3-DS completion, settle, refund, invoices, QR, P2P transfers, currency rates.
|
|
16
|
+
- **Recurring payments** — status, suspend, resume, remove, change.
|
|
17
|
+
- **Reporting** — check status, transaction list.
|
|
18
|
+
- **Merchant management (MMS)** — merchants, partners, balances.
|
|
19
|
+
- **Antifraud** — validate and check status.
|
|
20
|
+
- **Webhooks** — verify callbacks and acks, build acks.
|
|
21
|
+
- Fully typed (`py.typed`), documented, sync + async APIs in lockstep.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install pywayforpay
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires Python 3.11+.
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from wayforpay import Client, Credentials, money
|
|
35
|
+
from wayforpay.models import PurchaseRequest, Product, Currency
|
|
36
|
+
|
|
37
|
+
creds = Credentials(
|
|
38
|
+
merchant_account="test_merch_n1",
|
|
39
|
+
merchant_secret_key="flk3409refn54t54t*FNJRET",
|
|
40
|
+
merchant_domain_name="www.market.ua",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
product = Product(name="Dell XPS", price=money("1547.36"), count=1)
|
|
44
|
+
request = PurchaseRequest.from_products(
|
|
45
|
+
order_reference="DH783023",
|
|
46
|
+
order_date=1415379863,
|
|
47
|
+
amount=money("1547.36"),
|
|
48
|
+
currency=Currency.UAH,
|
|
49
|
+
products=[product],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
with Client(creds) as client:
|
|
53
|
+
url = client.purchase_url() # https://secure.wayforpay.com/pay
|
|
54
|
+
form = client.purchase_form(request) # signed POST payload for the payment page
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Render `form` as hidden fields inside `<form action="{url}" method="POST">`.
|
|
58
|
+
|
|
59
|
+
Async mirrors sync:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import asyncio
|
|
63
|
+
from wayforpay import AsyncClient, Credentials
|
|
64
|
+
|
|
65
|
+
async def main() -> None:
|
|
66
|
+
async with AsyncClient(creds) as client:
|
|
67
|
+
result = await client.check_status(CheckStatusRequest(order_reference="DH783023"))
|
|
68
|
+
|
|
69
|
+
asyncio.run(main())
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Examples
|
|
73
|
+
|
|
74
|
+
Runnable examples live in [`examples/`](examples/):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uv run python examples/purchase_form.py # signed purchase form + HTML snippet
|
|
78
|
+
uv run python examples/widget.py # payment-widget config
|
|
79
|
+
uv run python examples/webhooks.py # callback verification + ack
|
|
80
|
+
uv run python examples/verify_form.py # card verification form
|
|
81
|
+
uv run python examples/async_api.py # async client (dry-run; add --live)
|
|
82
|
+
uv run python examples/charge.py # host-to-host charge (dry-run; add --live)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Documentation
|
|
86
|
+
|
|
87
|
+
Full documentation is available at <https://HermanPlay.github.io/pywayforpay/>.
|
|
88
|
+
|
|
89
|
+
- [Quickstart](https://HermanPlay.github.io/pywayforpay/quickstart/)
|
|
90
|
+
- [Payments](https://HermanPlay.github.io/pywayforpay/payment-flows/)
|
|
91
|
+
- [Payment widget](https://HermanPlay.github.io/pywayforpay/widget/)
|
|
92
|
+
- [Webhooks](https://HermanPlay.github.io/pywayforpay/webhooks/)
|
|
93
|
+
- [Recurring payments](https://HermanPlay.github.io/pywayforpay/recurring/)
|
|
94
|
+
- [Merchant management (MMS)](https://HermanPlay.github.io/pywayforpay/mms/)
|
|
95
|
+
- [Antifraud](https://HermanPlay.github.io/pywayforpay/antifraud/)
|
|
96
|
+
- [Errors & reason codes](https://HermanPlay.github.io/pywayforpay/errors/)
|
|
97
|
+
- [PCI DSS](https://HermanPlay.github.io/pywayforpay/pci-dss/)
|
|
98
|
+
- [API reference](https://HermanPlay.github.io/pywayforpay/api/clients/)
|
|
99
|
+
|
|
100
|
+
WayForPay API docs: <https://wiki.wayforpay.com/en/>
|
|
101
|
+
|
|
102
|
+
## PCI DSS
|
|
103
|
+
|
|
104
|
+
This library exposes host-to-host card operations (`ChargeRequest`, `CardData`).
|
|
105
|
+
Handling raw card data carries PCI DSS obligations; the merchant/integrator remains
|
|
106
|
+
responsible for compliance. Prefer the hosted payment widget or redirect form to avoid
|
|
107
|
+
touching card data. See the [PCI DSS guide](https://HermanPlay.github.io/pywayforpay/pci-dss/).
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
Install dev deps: `uv sync`.
|
|
112
|
+
|
|
113
|
+
### Conventional commits
|
|
114
|
+
|
|
115
|
+
Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/)
|
|
116
|
+
(`feat:`, `fix:`, `docs:`, `refactor:`, `ci:`, ...). The `commit-lint` CI job checks
|
|
117
|
+
every commit in a PR (and the PR title) with commitizen and fails on violations.
|
|
118
|
+
|
|
119
|
+
The `master` branch is protected by a GitHub ruleset (`scripts/apply-protection.sh`):
|
|
120
|
+
|
|
121
|
+
- direct pushes and force-pushes to `master` are rejected — all changes go through PRs;
|
|
122
|
+
- a PR needs one approving review and a green `commit-lint` check before merge.
|
|
123
|
+
|
|
124
|
+
So the remote rejects non-conventional commits even if a contributor skips local hooks.
|
|
125
|
+
The pre-commit hook is convenience only:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
uv run pre-commit install --hook-type commit-msg # one-time local setup
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Releases
|
|
132
|
+
|
|
133
|
+
Publishing creates a version tag, a GitHub release with a changelog generated from
|
|
134
|
+
the commit log, and a PyPI package published via trusted publishing (OIDC).
|
|
135
|
+
|
|
136
|
+
1. Bump the version and tag it:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
uv run cz bump
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Bumps `pyproject.toml`, `__init__.py` and `uv.lock`, updates `CHANGELOG.md`, and
|
|
143
|
+
creates a `vX.Y.Z` tag. Add `--yes --increment patch|minor|major` to skip the
|
|
144
|
+
interactive prompt (e.g. in scripts/CI).
|
|
145
|
+
|
|
146
|
+
2. Push the tag:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
git push origin master --tags
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The `release` workflow builds the changelog for the tag and creates a GitHub
|
|
153
|
+
release. The `publish` workflow then publishes the package to PyPI.
|
|
154
|
+
|
|
155
|
+
#### One-time PyPI setup (trusted publishing)
|
|
156
|
+
|
|
157
|
+
The `publish` workflow uses PyPI trusted publishing (OIDC) — no API tokens stored.
|
|
158
|
+
|
|
159
|
+
- Enable 2FA on your PyPI and TestPyPI accounts (PyPI policy requirement).
|
|
160
|
+
- Register the project `pywayforpay` on PyPI (and TestPyPI).
|
|
161
|
+
- Add a trusted publisher on each: owner `HermanPlay`, repository `pywayforpay`,
|
|
162
|
+
workflow file `publish.yml`, environment `release`.
|
|
163
|
+
|
|
164
|
+
To validate a release against TestPyPI first, trigger the `publish` workflow manually
|
|
165
|
+
with repository `testpypi` (Actions tab → Publish to PyPI → Run workflow).
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Antifraud
|
|
2
|
+
|
|
3
|
+
The antifraud API (`AntifraudClient` / `AsyncAntifraudClient`) runs custom risk rules
|
|
4
|
+
against a proposed payment and reports back whether to accept or decline it.
|
|
5
|
+
|
|
6
|
+
It uses its own `login` / `password` (not merchant credentials):
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
from wayforpay import AntifraudClient
|
|
10
|
+
from wayforpay.models import AfValidateRequest
|
|
11
|
+
|
|
12
|
+
with AntifraudClient(login="af_login", password="af_password") as client:
|
|
13
|
+
result = client.validate(AfValidateRequest(...))
|
|
14
|
+
print(result.result) # AfResult.SUCCESS == 10
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Methods
|
|
18
|
+
|
|
19
|
+
- `validate(request, *, verify=False)` — run validation rules; returns `AfResponse`.
|
|
20
|
+
- `check_status(request, *, verify=False)` — check the status of a prior validation.
|
|
21
|
+
|
|
22
|
+
Setting `verify=True` forces a live check against the card issuer.
|
|
23
|
+
|
|
24
|
+
## Result codes
|
|
25
|
+
|
|
26
|
+
`AfResponse.result` is an `AfResult` (`IntEnum`):
|
|
27
|
+
|
|
28
|
+
- `AfResult.SUCCESS` (`10`) — pass.
|
|
29
|
+
- Anything else is treated as failure and raises `ResponseError`.
|
|
30
|
+
|
|
31
|
+
Async equivalents are on `AsyncAntifraudClient`.
|
|
32
|
+
|
|
33
|
+
## Reference
|
|
34
|
+
|
|
35
|
+
- WayForPay antifraud docs: <https://wiki.wayforpay.com/en/view/852223>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Clients
|
|
2
|
+
|
|
3
|
+
## Sync
|
|
4
|
+
|
|
5
|
+
::: wayforpay.Client
|
|
6
|
+
::: wayforpay.MMSClient
|
|
7
|
+
::: wayforpay.AntifraudClient
|
|
8
|
+
|
|
9
|
+
## Async
|
|
10
|
+
|
|
11
|
+
::: wayforpay.AsyncClient
|
|
12
|
+
::: wayforpay.AsyncMMSClient
|
|
13
|
+
::: wayforpay.AsyncAntifraudClient
|
|
14
|
+
|
|
15
|
+
## Credentials
|
|
16
|
+
|
|
17
|
+
::: wayforpay.Credentials
|