payshen 0.1.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.
- payshen-0.1.0/.gitattributes +1 -0
- payshen-0.1.0/.github/workflows/ci.yml +25 -0
- payshen-0.1.0/.gitignore +7 -0
- payshen-0.1.0/CHANGELOG.md +16 -0
- payshen-0.1.0/LICENSE +21 -0
- payshen-0.1.0/PKG-INFO +165 -0
- payshen-0.1.0/README.md +143 -0
- payshen-0.1.0/pyproject.toml +38 -0
- payshen-0.1.0/src/payshen/__init__.py +45 -0
- payshen-0.1.0/src/payshen/_client.py +370 -0
- payshen-0.1.0/src/payshen/_http.py +77 -0
- payshen-0.1.0/src/payshen/_version.py +1 -0
- payshen-0.1.0/src/payshen/errors.py +71 -0
- payshen-0.1.0/src/payshen/py.typed +0 -0
- payshen-0.1.0/src/payshen/webhooks.py +158 -0
- payshen-0.1.0/tests/test_client.py +232 -0
- payshen-0.1.0/tests/test_live_sandbox.py +48 -0
- payshen-0.1.0/tests/test_webhooks.py +94 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* text=auto eol=lf
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# Public repository: GitHub-hosted runners are free here. Still started by
|
|
4
|
+
# hand or by a pull request only, never by every push.
|
|
5
|
+
on:
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python }}
|
|
24
|
+
- run: python -m pip install -e ".[dev]"
|
|
25
|
+
- run: python -m pytest -q
|
payshen-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
The first release, with the same rules as the TypeScript SDK (`@payshen/sdk` 0.9.3).
|
|
6
|
+
|
|
7
|
+
- Payments: create, get, list, `all()` over every page, events, capture (full or partial), void, refund (full or
|
|
8
|
+
partial).
|
|
9
|
+
- Events feed, insights, suggestions (list, get, accept, reject, snooze), partner merchants.
|
|
10
|
+
- An `Idempotency-Key` on every POST, yours or generated and reused across retries.
|
|
11
|
+
- Retries on 408, 425, 429, 500, 502, 503 and 504 with backoff and jitter, never on an outcome the API says not to
|
|
12
|
+
retry (`payment_result_unknown`, `*_result_unknown`, `state_unconfirmed`, `state_conflict`, `psp_error`,
|
|
13
|
+
`provider_unavailable`, `merchant_limit`). `Retry-After` is honoured, capped at 4 seconds.
|
|
14
|
+
- Webhooks: `verify_webhook_signature`, `parse_webhook_event`, `claim_webhook_event` with a store, and secret
|
|
15
|
+
rotation.
|
|
16
|
+
- HTTPS only, except localhost; redirects are never followed. No dependencies.
|
payshen-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Payshen
|
|
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.
|
payshen-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: payshen
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the Payshen payment orchestration API: payments, captures, voids, refunds, events and webhook verification.
|
|
5
|
+
Project-URL: Homepage, https://www.payshen.com/developers
|
|
6
|
+
Project-URL: Documentation, https://www.payshen.com/developers
|
|
7
|
+
Project-URL: API reference, https://www.payshen.com/api/v1/openapi.json
|
|
8
|
+
Author: Sodasoft LLC
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: payment orchestration,payments,payshen,psp,webhooks
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Payshen Python
|
|
24
|
+
|
|
25
|
+
The Python client for the [Payshen](https://www.payshen.com) API: create and route payments, capture, void and
|
|
26
|
+
refund them, read their timeline, and verify the webhooks Payshen sends.
|
|
27
|
+
|
|
28
|
+
Payshen routes each payment to one of the providers you already have (Stripe, Adyen, Checkout.com, payabl. and
|
|
29
|
+
more). Payshen never holds or moves money: payments run on your own provider accounts.
|
|
30
|
+
|
|
31
|
+
- Python 3.9 or newer. No dependencies: the standard library only.
|
|
32
|
+
- Every POST carries an `Idempotency-Key`, so a retry after a dropped connection never charges twice.
|
|
33
|
+
- Retries with backoff and jitter, on the answers worth retrying and never on the ones the API says not to.
|
|
34
|
+
- Typed exceptions with the API's own error code and the request id support will ask for.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install payshen
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Create a payment
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import os
|
|
46
|
+
from payshen import Payshen
|
|
47
|
+
|
|
48
|
+
payshen = Payshen(os.environ["PAYSHEN_API_KEY"]) # ps_test_... in the sandbox
|
|
49
|
+
|
|
50
|
+
payment = payshen.payments.create(
|
|
51
|
+
{
|
|
52
|
+
"amount": 5000, # minor units: EUR 50.00
|
|
53
|
+
"currency": "EUR",
|
|
54
|
+
"country": "DE",
|
|
55
|
+
"reference": "order-1024",
|
|
56
|
+
"capture_method": "manual", # hold now, capture when you ship
|
|
57
|
+
"return_url": "https://yourshop.com/checkout/return",
|
|
58
|
+
},
|
|
59
|
+
idempotency_key="order-1024",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# REQUIRES_ACTION: send the customer to the provider's page.
|
|
63
|
+
redirect_to = payment["next_action"]["url"]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The customer pays on the provider's page and comes back to your `return_url`. The return is not the outcome: the
|
|
67
|
+
outcome is the webhook (`payment.authorized`, `payment.captured`, `payment.declined`, ...).
|
|
68
|
+
|
|
69
|
+
## Capture, void, refund
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from payshen import PayshenError
|
|
73
|
+
|
|
74
|
+
payshen.payments.capture(payment_id, idempotency_key="capture-order-1024") # in full
|
|
75
|
+
payshen.payments.capture(payment_id, amount=3000, idempotency_key="capture-order-1024") # or in part
|
|
76
|
+
payshen.payments.void(payment_id, idempotency_key="void-order-1024") # before capture
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
payshen.payments.refund(payment_id, amount=2000, idempotency_key="refund-order-1024-1")
|
|
80
|
+
except PayshenError as e:
|
|
81
|
+
# 409 refund_pending: accepted, not failed. The provider confirms a moment later and
|
|
82
|
+
# payment.refunded or payment.refund_failed follows. Do not send it again under a new key.
|
|
83
|
+
if e.code != "refund_pending":
|
|
84
|
+
raise
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
A capture or void a provider only accepted answers with the payment `PENDING`; it becomes `CAPTURED` or
|
|
88
|
+
`CANCELLED` when the provider confirms, and the webhook follows.
|
|
89
|
+
|
|
90
|
+
## Read
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
payment = payshen.payments.get(payment_id)
|
|
94
|
+
timeline = payshen.payments.events(payment_id)
|
|
95
|
+
|
|
96
|
+
page = payshen.payments.list(limit=50, status="captured")
|
|
97
|
+
for payment in payshen.payments.all(status="captured"):
|
|
98
|
+
... # every page, newest first
|
|
99
|
+
|
|
100
|
+
for event in payshen.events.all(type="payment.captured"):
|
|
101
|
+
... # the same events webhooks deliver, for polling
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Webhooks
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from payshen import SignatureVerificationError, parse_webhook_event
|
|
108
|
+
|
|
109
|
+
# Flask. Django: request.body and request.headers["X-Payshen-Signature"].
|
|
110
|
+
raw = request.get_data() # the RAW body: a body re-encoded from json never verifies
|
|
111
|
+
try:
|
|
112
|
+
event = parse_webhook_event(raw, request.headers.get("X-Payshen-Signature"), os.environ["PAYSHEN_WEBHOOK_SECRET"])
|
|
113
|
+
except SignatureVerificationError:
|
|
114
|
+
return "", 400
|
|
115
|
+
|
|
116
|
+
# Deliveries are retried, so the same event can arrive twice. Record event["id"]
|
|
117
|
+
# and skip one you have already processed.
|
|
118
|
+
if event["type"] == "payment.captured":
|
|
119
|
+
fulfil(event["data"]["reference"])
|
|
120
|
+
return "", 204
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The signature proves a delivery came from Payshen and is recent (5 minutes by default); it does not make it new.
|
|
124
|
+
`claim_webhook_event(raw, header, secret, store)` verifies, parses and takes each event once, with a store you back
|
|
125
|
+
with your database:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
class DbEventStore:
|
|
129
|
+
def claim(self, event_id: str) -> bool:
|
|
130
|
+
# PostgreSQL; MySQL: INSERT IGNORE and the affected row count
|
|
131
|
+
cur.execute("INSERT INTO processed_webhook_events (id) VALUES (%s) ON CONFLICT (id) DO NOTHING RETURNING id", (event_id,))
|
|
132
|
+
return cur.fetchone() is not None
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
During a secret rotation pass both: `parse_webhook_event(raw, header, [new_secret, old_secret])`.
|
|
136
|
+
|
|
137
|
+
## Errors
|
|
138
|
+
|
|
139
|
+
| Exception | When |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `AuthenticationError` | 401 or 403: a missing, wrong or revoked key, or one not allowed this call |
|
|
142
|
+
| `ValidationError` | 422: refused as sent; `code` names the rule |
|
|
143
|
+
| `RateLimitError` | 429 after the client's retries; `retry_after_seconds` when the API said |
|
|
144
|
+
| `NetworkError` | No answer at all. On a POST, retry with the same idempotency key |
|
|
145
|
+
| `PayshenError` | Everything else, and the parent of all of the above |
|
|
146
|
+
|
|
147
|
+
Every exception has `status`, `code` (`refund_pending`, `not_capturable`, `payment_result_unknown`, ...) and
|
|
148
|
+
`request_id`. Branch on `code`, not on the message.
|
|
149
|
+
|
|
150
|
+
## Options
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
Payshen(key, timeout=20.0, max_retries=3, base_url="https://www.payshen.com")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`payshen.request("GET", "/api/v1/...")` calls any endpoint and returns the whole answer.
|
|
157
|
+
|
|
158
|
+
## Documentation
|
|
159
|
+
|
|
160
|
+
- API reference and quickstart: <https://www.payshen.com/developers>
|
|
161
|
+
- OpenAPI 3.1: <https://www.payshen.com/api/v1/openapi.json>
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT
|
payshen-0.1.0/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Payshen Python
|
|
2
|
+
|
|
3
|
+
The Python client for the [Payshen](https://www.payshen.com) API: create and route payments, capture, void and
|
|
4
|
+
refund them, read their timeline, and verify the webhooks Payshen sends.
|
|
5
|
+
|
|
6
|
+
Payshen routes each payment to one of the providers you already have (Stripe, Adyen, Checkout.com, payabl. and
|
|
7
|
+
more). Payshen never holds or moves money: payments run on your own provider accounts.
|
|
8
|
+
|
|
9
|
+
- Python 3.9 or newer. No dependencies: the standard library only.
|
|
10
|
+
- Every POST carries an `Idempotency-Key`, so a retry after a dropped connection never charges twice.
|
|
11
|
+
- Retries with backoff and jitter, on the answers worth retrying and never on the ones the API says not to.
|
|
12
|
+
- Typed exceptions with the API's own error code and the request id support will ask for.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install payshen
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Create a payment
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import os
|
|
24
|
+
from payshen import Payshen
|
|
25
|
+
|
|
26
|
+
payshen = Payshen(os.environ["PAYSHEN_API_KEY"]) # ps_test_... in the sandbox
|
|
27
|
+
|
|
28
|
+
payment = payshen.payments.create(
|
|
29
|
+
{
|
|
30
|
+
"amount": 5000, # minor units: EUR 50.00
|
|
31
|
+
"currency": "EUR",
|
|
32
|
+
"country": "DE",
|
|
33
|
+
"reference": "order-1024",
|
|
34
|
+
"capture_method": "manual", # hold now, capture when you ship
|
|
35
|
+
"return_url": "https://yourshop.com/checkout/return",
|
|
36
|
+
},
|
|
37
|
+
idempotency_key="order-1024",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# REQUIRES_ACTION: send the customer to the provider's page.
|
|
41
|
+
redirect_to = payment["next_action"]["url"]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The customer pays on the provider's page and comes back to your `return_url`. The return is not the outcome: the
|
|
45
|
+
outcome is the webhook (`payment.authorized`, `payment.captured`, `payment.declined`, ...).
|
|
46
|
+
|
|
47
|
+
## Capture, void, refund
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from payshen import PayshenError
|
|
51
|
+
|
|
52
|
+
payshen.payments.capture(payment_id, idempotency_key="capture-order-1024") # in full
|
|
53
|
+
payshen.payments.capture(payment_id, amount=3000, idempotency_key="capture-order-1024") # or in part
|
|
54
|
+
payshen.payments.void(payment_id, idempotency_key="void-order-1024") # before capture
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
payshen.payments.refund(payment_id, amount=2000, idempotency_key="refund-order-1024-1")
|
|
58
|
+
except PayshenError as e:
|
|
59
|
+
# 409 refund_pending: accepted, not failed. The provider confirms a moment later and
|
|
60
|
+
# payment.refunded or payment.refund_failed follows. Do not send it again under a new key.
|
|
61
|
+
if e.code != "refund_pending":
|
|
62
|
+
raise
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A capture or void a provider only accepted answers with the payment `PENDING`; it becomes `CAPTURED` or
|
|
66
|
+
`CANCELLED` when the provider confirms, and the webhook follows.
|
|
67
|
+
|
|
68
|
+
## Read
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
payment = payshen.payments.get(payment_id)
|
|
72
|
+
timeline = payshen.payments.events(payment_id)
|
|
73
|
+
|
|
74
|
+
page = payshen.payments.list(limit=50, status="captured")
|
|
75
|
+
for payment in payshen.payments.all(status="captured"):
|
|
76
|
+
... # every page, newest first
|
|
77
|
+
|
|
78
|
+
for event in payshen.events.all(type="payment.captured"):
|
|
79
|
+
... # the same events webhooks deliver, for polling
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Webhooks
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from payshen import SignatureVerificationError, parse_webhook_event
|
|
86
|
+
|
|
87
|
+
# Flask. Django: request.body and request.headers["X-Payshen-Signature"].
|
|
88
|
+
raw = request.get_data() # the RAW body: a body re-encoded from json never verifies
|
|
89
|
+
try:
|
|
90
|
+
event = parse_webhook_event(raw, request.headers.get("X-Payshen-Signature"), os.environ["PAYSHEN_WEBHOOK_SECRET"])
|
|
91
|
+
except SignatureVerificationError:
|
|
92
|
+
return "", 400
|
|
93
|
+
|
|
94
|
+
# Deliveries are retried, so the same event can arrive twice. Record event["id"]
|
|
95
|
+
# and skip one you have already processed.
|
|
96
|
+
if event["type"] == "payment.captured":
|
|
97
|
+
fulfil(event["data"]["reference"])
|
|
98
|
+
return "", 204
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The signature proves a delivery came from Payshen and is recent (5 minutes by default); it does not make it new.
|
|
102
|
+
`claim_webhook_event(raw, header, secret, store)` verifies, parses and takes each event once, with a store you back
|
|
103
|
+
with your database:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
class DbEventStore:
|
|
107
|
+
def claim(self, event_id: str) -> bool:
|
|
108
|
+
# PostgreSQL; MySQL: INSERT IGNORE and the affected row count
|
|
109
|
+
cur.execute("INSERT INTO processed_webhook_events (id) VALUES (%s) ON CONFLICT (id) DO NOTHING RETURNING id", (event_id,))
|
|
110
|
+
return cur.fetchone() is not None
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
During a secret rotation pass both: `parse_webhook_event(raw, header, [new_secret, old_secret])`.
|
|
114
|
+
|
|
115
|
+
## Errors
|
|
116
|
+
|
|
117
|
+
| Exception | When |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `AuthenticationError` | 401 or 403: a missing, wrong or revoked key, or one not allowed this call |
|
|
120
|
+
| `ValidationError` | 422: refused as sent; `code` names the rule |
|
|
121
|
+
| `RateLimitError` | 429 after the client's retries; `retry_after_seconds` when the API said |
|
|
122
|
+
| `NetworkError` | No answer at all. On a POST, retry with the same idempotency key |
|
|
123
|
+
| `PayshenError` | Everything else, and the parent of all of the above |
|
|
124
|
+
|
|
125
|
+
Every exception has `status`, `code` (`refund_pending`, `not_capturable`, `payment_result_unknown`, ...) and
|
|
126
|
+
`request_id`. Branch on `code`, not on the message.
|
|
127
|
+
|
|
128
|
+
## Options
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
Payshen(key, timeout=20.0, max_retries=3, base_url="https://www.payshen.com")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`payshen.request("GET", "/api/v1/...")` calls any endpoint and returns the whole answer.
|
|
135
|
+
|
|
136
|
+
## Documentation
|
|
137
|
+
|
|
138
|
+
- API reference and quickstart: <https://www.payshen.com/developers>
|
|
139
|
+
- OpenAPI 3.1: <https://www.payshen.com/api/v1/openapi.json>
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
MIT
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.24"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "payshen"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python client for the Payshen payment orchestration API: payments, captures, voids, refunds, events and webhook verification."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
authors = [{ name = "Sodasoft LLC" }]
|
|
14
|
+
keywords = ["payshen", "payments", "payment orchestration", "psp", "webhooks"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Topic :: Office/Business :: Financial",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
dependencies = []
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://www.payshen.com/developers"
|
|
27
|
+
Documentation = "https://www.payshen.com/developers"
|
|
28
|
+
"API reference" = "https://www.payshen.com/api/v1/openapi.json"
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = ["pytest>=8"]
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/payshen"]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
testpaths = ["tests"]
|
|
38
|
+
filterwarnings = ["error"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Payshen: the Python client for the Payshen payment orchestration API."""
|
|
2
|
+
|
|
3
|
+
from ._client import Page, Payshen
|
|
4
|
+
from ._http import Response, Transport, TransportError, UrllibTransport
|
|
5
|
+
from ._version import VERSION
|
|
6
|
+
from .errors import (
|
|
7
|
+
AuthenticationError,
|
|
8
|
+
NetworkError,
|
|
9
|
+
PayshenError,
|
|
10
|
+
RateLimitError,
|
|
11
|
+
SignatureVerificationError,
|
|
12
|
+
ValidationError,
|
|
13
|
+
)
|
|
14
|
+
from .webhooks import (
|
|
15
|
+
DEFAULT_TOLERANCE_SECONDS,
|
|
16
|
+
MemoryWebhookEventStore,
|
|
17
|
+
WebhookEventStore,
|
|
18
|
+
claim_webhook_event,
|
|
19
|
+
parse_webhook_event,
|
|
20
|
+
verify_webhook_signature,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__version__ = VERSION
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"VERSION",
|
|
27
|
+
"Payshen",
|
|
28
|
+
"Page",
|
|
29
|
+
"PayshenError",
|
|
30
|
+
"AuthenticationError",
|
|
31
|
+
"SignatureVerificationError",
|
|
32
|
+
"ValidationError",
|
|
33
|
+
"RateLimitError",
|
|
34
|
+
"NetworkError",
|
|
35
|
+
"Transport",
|
|
36
|
+
"TransportError",
|
|
37
|
+
"Response",
|
|
38
|
+
"UrllibTransport",
|
|
39
|
+
"DEFAULT_TOLERANCE_SECONDS",
|
|
40
|
+
"verify_webhook_signature",
|
|
41
|
+
"parse_webhook_event",
|
|
42
|
+
"claim_webhook_event",
|
|
43
|
+
"WebhookEventStore",
|
|
44
|
+
"MemoryWebhookEventStore",
|
|
45
|
+
]
|