bepaid 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.
- bepaid-0.1.0/PKG-INFO +141 -0
- bepaid-0.1.0/README.md +127 -0
- bepaid-0.1.0/pyproject.toml +26 -0
- bepaid-0.1.0/pyproject.toml.orig +27 -0
- bepaid-0.1.0/src/bepaid/__init__.py +7 -0
- bepaid-0.1.0/src/bepaid/client.py +210 -0
- bepaid-0.1.0/src/bepaid/errors.py +19 -0
- bepaid-0.1.0/src/bepaid/models.py +371 -0
bepaid-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bepaid
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Client for the bePaid payment API (bepaid.by)
|
|
5
|
+
Author: amnesiaof
|
|
6
|
+
Author-email: amnesiaof <dolombovskyyalexs@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Dist: httpx>=0.27
|
|
9
|
+
Requires-Dist: pydantic>=2.7
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Project-URL: repository, https://github.com/amnesiaof/bepaid-py
|
|
12
|
+
Project-URL: homepage, https://github.com/amnesiaof/bepaid-py
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# bepaid
|
|
16
|
+
|
|
17
|
+
Python client for the [bePaid payment API](https://docs.bepaid.by) (bepaid.by).
|
|
18
|
+
|
|
19
|
+
Covers the Gateway API (card payments, tokenization, capture, void, refunds),
|
|
20
|
+
the hosted Checkout API, and the Direct/APM API (alternative payment methods).
|
|
21
|
+
Built on `httpx` and `pydantic`.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv add bepaid
|
|
27
|
+
# or
|
|
28
|
+
pip install bepaid
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Requires Python 3.10+.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from bepaid import BepaidClient
|
|
37
|
+
from bepaid.models import PaymentRequest, CreditCardRaw
|
|
38
|
+
|
|
39
|
+
client = BepaidClient("shop_id", "secret_key")
|
|
40
|
+
|
|
41
|
+
# amounts are strings in minor units, e.g. "700" = 7.00 BYN
|
|
42
|
+
payment = client.create_payment(
|
|
43
|
+
PaymentRequest(
|
|
44
|
+
amount="700",
|
|
45
|
+
currency="BYN",
|
|
46
|
+
test=True,
|
|
47
|
+
description="Order #123",
|
|
48
|
+
tracking_id="order-123",
|
|
49
|
+
credit_card=CreditCardRaw(
|
|
50
|
+
number="4242424242424242",
|
|
51
|
+
verification_value="123",
|
|
52
|
+
holder="John Smith",
|
|
53
|
+
exp_month=10,
|
|
54
|
+
exp_year=2030,
|
|
55
|
+
save_card=True,
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Use the client as a context manager to close the underlying HTTP connection:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
with BepaidClient("shop_id", "secret_key") as client:
|
|
65
|
+
payment = client.create_payment(...)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Authorization with 3-D Secure
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
auth = client.create_authorization(AuthorizationRequest(amount=700, currency="BYN", ...))
|
|
72
|
+
# redirect the customer to auth.redirect_url, then poll for the result:
|
|
73
|
+
tx = client.get_transaction(auth.uid)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Hosted checkout
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
checkout = client.create_checkout(
|
|
80
|
+
CheckoutRequest(
|
|
81
|
+
transaction_type="payment",
|
|
82
|
+
order=CheckoutOrder(currency="BYN", amount=700, description="Order #123"),
|
|
83
|
+
settings=CheckoutSettings(
|
|
84
|
+
return_url="https://example.com/return",
|
|
85
|
+
notification_url="https://example.com/webhook",
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
# redirect the customer to checkout.redirect_url
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Tokenization
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
token = client.create_token(
|
|
96
|
+
CreateTokenRequest(
|
|
97
|
+
number="4200000000000000",
|
|
98
|
+
holder="John Smith",
|
|
99
|
+
exp_month="05",
|
|
100
|
+
exp_year="2028",
|
|
101
|
+
contract=["recurring"],
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
# store token.token, use it later without re-entering card details
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### APM payments (Direct API)
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
apm = client.create_apm_payment(
|
|
111
|
+
ApmPaymentRequest(
|
|
112
|
+
amount=700,
|
|
113
|
+
currency="BYN",
|
|
114
|
+
payment_method={"type": "erip", "service_no": "0000000001"},
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Webhooks
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from bepaid.client import verify_webhook_auth
|
|
123
|
+
from bepaid.models import WebhookNotification
|
|
124
|
+
|
|
125
|
+
assert verify_webhook_auth(authorization_header, shop_id, secret_key)
|
|
126
|
+
notification = WebhookNotification.model_validate_json(raw_body)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## API coverage
|
|
130
|
+
|
|
131
|
+
| Group | Operations |
|
|
132
|
+
|-----------|------------|
|
|
133
|
+
| Gateway | `create_payment`, `create_authorization`, `capture`, `void`, `refund`, `get_transaction` |
|
|
134
|
+
| Tokens | `create_token` |
|
|
135
|
+
| Checkout | `create_checkout`, `get_checkout_status`, `validate_apple_pay` |
|
|
136
|
+
| Direct | `create_apm_payment`, `apm_refund`, `apm_full_refund` |
|
|
137
|
+
| Webhooks | verification + payload parsing |
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT
|
bepaid-0.1.0/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# bepaid
|
|
2
|
+
|
|
3
|
+
Python client for the [bePaid payment API](https://docs.bepaid.by) (bepaid.by).
|
|
4
|
+
|
|
5
|
+
Covers the Gateway API (card payments, tokenization, capture, void, refunds),
|
|
6
|
+
the hosted Checkout API, and the Direct/APM API (alternative payment methods).
|
|
7
|
+
Built on `httpx` and `pydantic`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uv add bepaid
|
|
13
|
+
# or
|
|
14
|
+
pip install bepaid
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires Python 3.10+.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from bepaid import BepaidClient
|
|
23
|
+
from bepaid.models import PaymentRequest, CreditCardRaw
|
|
24
|
+
|
|
25
|
+
client = BepaidClient("shop_id", "secret_key")
|
|
26
|
+
|
|
27
|
+
# amounts are strings in minor units, e.g. "700" = 7.00 BYN
|
|
28
|
+
payment = client.create_payment(
|
|
29
|
+
PaymentRequest(
|
|
30
|
+
amount="700",
|
|
31
|
+
currency="BYN",
|
|
32
|
+
test=True,
|
|
33
|
+
description="Order #123",
|
|
34
|
+
tracking_id="order-123",
|
|
35
|
+
credit_card=CreditCardRaw(
|
|
36
|
+
number="4242424242424242",
|
|
37
|
+
verification_value="123",
|
|
38
|
+
holder="John Smith",
|
|
39
|
+
exp_month=10,
|
|
40
|
+
exp_year=2030,
|
|
41
|
+
save_card=True,
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use the client as a context manager to close the underlying HTTP connection:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
with BepaidClient("shop_id", "secret_key") as client:
|
|
51
|
+
payment = client.create_payment(...)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Authorization with 3-D Secure
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
auth = client.create_authorization(AuthorizationRequest(amount=700, currency="BYN", ...))
|
|
58
|
+
# redirect the customer to auth.redirect_url, then poll for the result:
|
|
59
|
+
tx = client.get_transaction(auth.uid)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Hosted checkout
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
checkout = client.create_checkout(
|
|
66
|
+
CheckoutRequest(
|
|
67
|
+
transaction_type="payment",
|
|
68
|
+
order=CheckoutOrder(currency="BYN", amount=700, description="Order #123"),
|
|
69
|
+
settings=CheckoutSettings(
|
|
70
|
+
return_url="https://example.com/return",
|
|
71
|
+
notification_url="https://example.com/webhook",
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
# redirect the customer to checkout.redirect_url
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Tokenization
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
token = client.create_token(
|
|
82
|
+
CreateTokenRequest(
|
|
83
|
+
number="4200000000000000",
|
|
84
|
+
holder="John Smith",
|
|
85
|
+
exp_month="05",
|
|
86
|
+
exp_year="2028",
|
|
87
|
+
contract=["recurring"],
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
# store token.token, use it later without re-entering card details
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### APM payments (Direct API)
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
apm = client.create_apm_payment(
|
|
97
|
+
ApmPaymentRequest(
|
|
98
|
+
amount=700,
|
|
99
|
+
currency="BYN",
|
|
100
|
+
payment_method={"type": "erip", "service_no": "0000000001"},
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Webhooks
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from bepaid.client import verify_webhook_auth
|
|
109
|
+
from bepaid.models import WebhookNotification
|
|
110
|
+
|
|
111
|
+
assert verify_webhook_auth(authorization_header, shop_id, secret_key)
|
|
112
|
+
notification = WebhookNotification.model_validate_json(raw_body)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## API coverage
|
|
116
|
+
|
|
117
|
+
| Group | Operations |
|
|
118
|
+
|-----------|------------|
|
|
119
|
+
| Gateway | `create_payment`, `create_authorization`, `capture`, `void`, `refund`, `get_transaction` |
|
|
120
|
+
| Tokens | `create_token` |
|
|
121
|
+
| Checkout | `create_checkout`, `get_checkout_status`, `validate_apple_pay` |
|
|
122
|
+
| Direct | `create_apm_payment`, `apm_refund`, `apm_full_refund` |
|
|
123
|
+
| Webhooks | verification + payload parsing |
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "bepaid"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Client for the bePaid payment API (bepaid.by)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"httpx>=0.27",
|
|
10
|
+
"pydantic>=2.7",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[[project.authors]]
|
|
14
|
+
name = "amnesiaof"
|
|
15
|
+
email = "dolombovskyyalexs@gmail.com"
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
repository = "https://github.com/amnesiaof/bepaid-py"
|
|
19
|
+
homepage = "https://github.com/amnesiaof/bepaid-py"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["uv_build>=0.12.13,<0.13.0"]
|
|
23
|
+
build-backend = "uv_build"
|
|
24
|
+
|
|
25
|
+
[dependency-groups]
|
|
26
|
+
dev = ["pytest>=9.1.1"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "bepaid"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Client for the bePaid payment API (bepaid.by)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "amnesiaof", email = "dolombovskyyalexs@gmail.com" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"httpx>=0.27",
|
|
13
|
+
"pydantic>=2.7",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
repository = "https://github.com/amnesiaof/bepaid-py"
|
|
18
|
+
homepage = "https://github.com/amnesiaof/bepaid-py"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["uv_build>=0.12.13,<0.13.0"]
|
|
22
|
+
build-backend = "uv_build"
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = [
|
|
26
|
+
"pytest>=9.1.1",
|
|
27
|
+
]
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""httpx-based client for the bePaid API (bepaid.by)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
from typing import Any, Self
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from .errors import ApiError
|
|
12
|
+
from .models import (
|
|
13
|
+
ApmPaymentRequest,
|
|
14
|
+
ApmPaymentResponse,
|
|
15
|
+
ApmRefundRequest,
|
|
16
|
+
ApmRefundResponse,
|
|
17
|
+
AuthorizationRequest,
|
|
18
|
+
AuthorizationResponse,
|
|
19
|
+
CaptureRequest,
|
|
20
|
+
CaptureResponse,
|
|
21
|
+
CheckoutRequest,
|
|
22
|
+
CheckoutResponse,
|
|
23
|
+
CheckoutStatus,
|
|
24
|
+
CreateTokenRequest,
|
|
25
|
+
PaymentRequest,
|
|
26
|
+
PaymentResponse,
|
|
27
|
+
RefundRequest,
|
|
28
|
+
RefundResponse,
|
|
29
|
+
TokenResponse,
|
|
30
|
+
Transaction,
|
|
31
|
+
VoidRequest,
|
|
32
|
+
VoidResponse,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
DEFAULT_GATEWAY_URL = "https://gateway.bepaid.by"
|
|
36
|
+
DEFAULT_CHECKOUT_URL = "https://checkout.bepaid.by"
|
|
37
|
+
DEFAULT_API_URL = "https://api.bepaid.by"
|
|
38
|
+
|
|
39
|
+
_HEADERS = {"Content-Type": "application/json", "Accept": "application/json"}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class BepaidClient:
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
shop_id: str | int,
|
|
46
|
+
secret_key: str,
|
|
47
|
+
*,
|
|
48
|
+
timeout: float = 30.0,
|
|
49
|
+
base_gateway_url: str = DEFAULT_GATEWAY_URL,
|
|
50
|
+
base_checkout_url: str = DEFAULT_CHECKOUT_URL,
|
|
51
|
+
base_api_url: str = DEFAULT_API_URL,
|
|
52
|
+
) -> None:
|
|
53
|
+
credentials = f"{shop_id}:{secret_key}"
|
|
54
|
+
self._auth = "Basic " + base64.b64encode(credentials.encode()).decode()
|
|
55
|
+
self._base_gateway = base_gateway_url
|
|
56
|
+
self._base_checkout = base_checkout_url
|
|
57
|
+
self._base_api = base_api_url
|
|
58
|
+
self._http = httpx.Client(timeout=timeout, headers=_HEADERS)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def http(self) -> httpx.Client:
|
|
62
|
+
"""The underlying synchronous httpx client (for advanced use)."""
|
|
63
|
+
return self._http
|
|
64
|
+
|
|
65
|
+
def close(self) -> None:
|
|
66
|
+
self._http.close()
|
|
67
|
+
|
|
68
|
+
def __enter__(self) -> Self:
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def __exit__(self, *exc: object) -> None:
|
|
72
|
+
self.close()
|
|
73
|
+
|
|
74
|
+
# ── transport ──────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
def _request(
|
|
77
|
+
self, method: str, url: str, body: BaseModel | dict | None = None
|
|
78
|
+
) -> dict[str, Any]:
|
|
79
|
+
resp = self._http.request(
|
|
80
|
+
method,
|
|
81
|
+
url,
|
|
82
|
+
headers={"Authorization": self._auth},
|
|
83
|
+
json=body.model_dump(by_alias=True, exclude_none=True)
|
|
84
|
+
if isinstance(body, BaseModel)
|
|
85
|
+
else body,
|
|
86
|
+
)
|
|
87
|
+
if resp.status_code >= 400:
|
|
88
|
+
data = resp.json()
|
|
89
|
+
raise ApiError(
|
|
90
|
+
status=resp.status_code,
|
|
91
|
+
message=data.get("message", resp.text),
|
|
92
|
+
errors=data.get("errors"),
|
|
93
|
+
)
|
|
94
|
+
return resp.json()
|
|
95
|
+
|
|
96
|
+
# ── gateway API ────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
def create_payment(self, req: PaymentRequest) -> PaymentResponse:
|
|
99
|
+
data = self._request(
|
|
100
|
+
"POST",
|
|
101
|
+
f"{self._base_gateway}/transactions/payments",
|
|
102
|
+
{"request": req.model_dump(by_alias=True, exclude_none=True)},
|
|
103
|
+
)
|
|
104
|
+
return PaymentResponse.model_validate(data["transaction"])
|
|
105
|
+
|
|
106
|
+
def create_authorization(self, req: AuthorizationRequest) -> AuthorizationResponse:
|
|
107
|
+
data = self._request(
|
|
108
|
+
"POST",
|
|
109
|
+
f"{self._base_gateway}/transactions/authorizations",
|
|
110
|
+
{"request": req.model_dump(by_alias=True, exclude_none=True)},
|
|
111
|
+
)
|
|
112
|
+
return AuthorizationResponse.model_validate(data["transaction"])
|
|
113
|
+
|
|
114
|
+
def capture(self, req: CaptureRequest) -> CaptureResponse:
|
|
115
|
+
data = self._request(
|
|
116
|
+
"POST",
|
|
117
|
+
f"{self._base_gateway}/transactions/captures",
|
|
118
|
+
{"request": req.model_dump(by_alias=True, exclude_none=True)},
|
|
119
|
+
)
|
|
120
|
+
return CaptureResponse.model_validate(data["transaction"])
|
|
121
|
+
|
|
122
|
+
def void(self, req: VoidRequest) -> VoidResponse:
|
|
123
|
+
data = self._request(
|
|
124
|
+
"POST",
|
|
125
|
+
f"{self._base_gateway}/transactions/voids",
|
|
126
|
+
{"request": req.model_dump(by_alias=True, exclude_none=True)},
|
|
127
|
+
)
|
|
128
|
+
return VoidResponse.model_validate(data["transaction"])
|
|
129
|
+
|
|
130
|
+
def refund(self, req: RefundRequest) -> RefundResponse:
|
|
131
|
+
data = self._request(
|
|
132
|
+
"POST",
|
|
133
|
+
f"{self._base_gateway}/transactions/refunds",
|
|
134
|
+
{"request": req.model_dump(by_alias=True, exclude_none=True)},
|
|
135
|
+
)
|
|
136
|
+
return RefundResponse.model_validate(data["transaction"])
|
|
137
|
+
|
|
138
|
+
def get_transaction(self, uid: str) -> Transaction:
|
|
139
|
+
data = self._request("GET", f"{self._base_gateway}/transactions/{uid}")
|
|
140
|
+
return Transaction.model_validate(data["transaction"])
|
|
141
|
+
|
|
142
|
+
# ── token API ──────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
def create_token(self, req: CreateTokenRequest) -> TokenResponse:
|
|
145
|
+
data = self._request(
|
|
146
|
+
"POST",
|
|
147
|
+
f"{self._base_gateway}/credit_cards",
|
|
148
|
+
{"request": req.model_dump(by_alias=True, exclude_none=True)},
|
|
149
|
+
)
|
|
150
|
+
return TokenResponse.model_validate(data)
|
|
151
|
+
|
|
152
|
+
# ── checkout API ───────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
def create_checkout(self, req: CheckoutRequest) -> CheckoutResponse:
|
|
155
|
+
data = self._request(
|
|
156
|
+
"POST",
|
|
157
|
+
f"{self._base_checkout}/ctp/api/checkouts",
|
|
158
|
+
{"checkout": req.model_dump(by_alias=True, exclude_none=True)},
|
|
159
|
+
)
|
|
160
|
+
return CheckoutResponse.model_validate(data["checkout"])
|
|
161
|
+
|
|
162
|
+
def get_checkout_status(self, token: str) -> CheckoutStatus:
|
|
163
|
+
data = self._request("GET", f"{self._base_checkout}/ctp/api/checkouts/{token}")
|
|
164
|
+
return CheckoutStatus.model_validate(data["checkout"])
|
|
165
|
+
|
|
166
|
+
def validate_apple_pay(self, url: str, token: str | None = None) -> dict[str, Any]:
|
|
167
|
+
return self._request(
|
|
168
|
+
"POST",
|
|
169
|
+
f"{self._base_checkout}/ctp/api/apple_pay/validate",
|
|
170
|
+
{"url": url, "token": token, "context": "merchant"},
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# ── direct / APM API ───────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
def create_apm_payment(self, req: ApmPaymentRequest) -> ApmPaymentResponse:
|
|
176
|
+
data = self._request(
|
|
177
|
+
"POST",
|
|
178
|
+
f"{self._base_api}/beyag/transactions/payments",
|
|
179
|
+
{"request": req.model_dump(by_alias=True, exclude_none=True)},
|
|
180
|
+
)
|
|
181
|
+
return ApmPaymentResponse.model_validate(data["transaction"])
|
|
182
|
+
|
|
183
|
+
def apm_refund(self, req: ApmRefundRequest) -> ApmRefundResponse:
|
|
184
|
+
data = self._request(
|
|
185
|
+
"POST",
|
|
186
|
+
f"{self._base_api}/beyag/transactions/refunds",
|
|
187
|
+
{"request": req.model_dump(by_alias=True, exclude_none=True)},
|
|
188
|
+
)
|
|
189
|
+
return ApmRefundResponse.model_validate(data["transaction"])
|
|
190
|
+
|
|
191
|
+
def apm_full_refund(
|
|
192
|
+
self, parent_uid: str, reason: str, amount: int | None = None
|
|
193
|
+
) -> ApmRefundResponse:
|
|
194
|
+
req = ApmRefundRequest(parent_uid=parent_uid, reason=reason, amount=amount)
|
|
195
|
+
data = self._request(
|
|
196
|
+
"POST",
|
|
197
|
+
f"{self._base_api}/beyag/refunds",
|
|
198
|
+
{"request": req.model_dump(by_alias=True, exclude_none=True)},
|
|
199
|
+
)
|
|
200
|
+
return ApmRefundResponse.model_validate(data["transaction"])
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# ── webhook helpers ──────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def verify_webhook_auth(
|
|
207
|
+
authorization_header: str, shop_id: str, secret_key: str
|
|
208
|
+
) -> bool:
|
|
209
|
+
expected = "Basic " + base64.b64encode(f"{shop_id}:{secret_key}".encode()).decode()
|
|
210
|
+
return authorization_header == expected
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BepaidError(Exception):
|
|
7
|
+
"""Base exception for bepaid errors."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ApiError(BepaidError):
|
|
12
|
+
"""An error response returned by the bePaid API."""
|
|
13
|
+
|
|
14
|
+
status: int
|
|
15
|
+
message: str
|
|
16
|
+
errors: dict | None = None
|
|
17
|
+
|
|
18
|
+
def __str__(self) -> str:
|
|
19
|
+
return f"API error {self.status}: {self.message}"
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"""Pydantic models for the bePaid API (aliases use camelCase as the API expects)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
8
|
+
from pydantic.alias_generators import to_camel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CamelModel(BaseModel):
|
|
12
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BillingAddress(CamelModel):
|
|
16
|
+
first_name: str | None = None
|
|
17
|
+
last_name: str | None = None
|
|
18
|
+
country: str | None = None
|
|
19
|
+
city: str | None = None
|
|
20
|
+
state: str | None = None
|
|
21
|
+
zip: str | None = None
|
|
22
|
+
address: str | None = None
|
|
23
|
+
phone: str | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Customer(CamelModel):
|
|
27
|
+
first_name: str | None = None
|
|
28
|
+
last_name: str | None = None
|
|
29
|
+
middle_name: str | None = None
|
|
30
|
+
ip: str | None = None
|
|
31
|
+
email: str | None = None
|
|
32
|
+
device_id: str | None = None
|
|
33
|
+
birth_date: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BrowserInfo(CamelModel):
|
|
37
|
+
screen_width: int | None = None
|
|
38
|
+
screen_height: int | None = None
|
|
39
|
+
screen_color_depth: int | None = None
|
|
40
|
+
language: str | None = None
|
|
41
|
+
java_enabled: bool | None = None
|
|
42
|
+
user_agent: str | None = None
|
|
43
|
+
time_zone: int | None = None
|
|
44
|
+
time_zone_name: str | None = None
|
|
45
|
+
accept_header: str | None = None
|
|
46
|
+
window_height: int | None = None
|
|
47
|
+
window_width: int | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AdditionalData(CamelModel):
|
|
51
|
+
browser: BrowserInfo | None = None
|
|
52
|
+
contract: list[str] | None = None
|
|
53
|
+
referer: str | None = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CreditCardRaw(CamelModel):
|
|
57
|
+
number: str
|
|
58
|
+
verification_value: str | None = None
|
|
59
|
+
holder: str | None = None
|
|
60
|
+
exp_month: int | None = None
|
|
61
|
+
exp_year: int | None = None
|
|
62
|
+
save_card: bool | None = None
|
|
63
|
+
token: str | None = None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CreditCardInfo(CamelModel):
|
|
67
|
+
holder: str | None = None
|
|
68
|
+
stamp: str | None = None
|
|
69
|
+
brand: str | None = None
|
|
70
|
+
last_4: str | None = None
|
|
71
|
+
first_1: str | None = None
|
|
72
|
+
bin: str | None = None
|
|
73
|
+
exp_month: int | None = None
|
|
74
|
+
exp_year: int | None = None
|
|
75
|
+
token: str | None = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class PaymentInfo(CamelModel):
|
|
79
|
+
auth_code: str | None = None
|
|
80
|
+
bank_code: str | None = None
|
|
81
|
+
rrn: str | None = None
|
|
82
|
+
ref_id: str | None = None
|
|
83
|
+
message: str | None = None
|
|
84
|
+
amount: int | None = None
|
|
85
|
+
currency: str | None = None
|
|
86
|
+
billing_descriptor: str | None = None
|
|
87
|
+
gateway_id: int | None = None
|
|
88
|
+
status: str | None = None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ThreeDSecureVerification(CamelModel):
|
|
92
|
+
status: str | None = None
|
|
93
|
+
message: str | None = None
|
|
94
|
+
pa_res_url: str | None = None
|
|
95
|
+
eci: str | None = None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ── gateway ───────────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class PaymentRequest(CamelModel):
|
|
102
|
+
amount: str
|
|
103
|
+
currency: str
|
|
104
|
+
test: bool
|
|
105
|
+
description: str
|
|
106
|
+
tracking_id: str
|
|
107
|
+
language: str | None = None
|
|
108
|
+
notification_url: str | None = None
|
|
109
|
+
billing_address: BillingAddress | None = None
|
|
110
|
+
credit_card: CreditCardRaw | None = None
|
|
111
|
+
customer: Customer | None = None
|
|
112
|
+
additional_data: AdditionalData | None = None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class PaymentResponse(CamelModel):
|
|
116
|
+
tracking_id: str | None = None
|
|
117
|
+
uid: str
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class AuthorizationRequest(CamelModel):
|
|
121
|
+
amount: int
|
|
122
|
+
currency: str
|
|
123
|
+
description: str
|
|
124
|
+
tracking_id: str
|
|
125
|
+
payment_method_type: str | None = None
|
|
126
|
+
test: bool | None = None
|
|
127
|
+
credit_card: CreditCardRaw | None = None
|
|
128
|
+
customer: Customer | None = None
|
|
129
|
+
billing_address: BillingAddress | None = None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class AuthorizationResponse(CamelModel):
|
|
133
|
+
uid: str
|
|
134
|
+
status: str | None = None
|
|
135
|
+
redirect_url: str | None = None
|
|
136
|
+
three_d_secure_verification: ThreeDSecureVerification | None = None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class Transaction(CamelModel):
|
|
140
|
+
uid: str
|
|
141
|
+
status: str | None = None
|
|
142
|
+
amount: int | None = None
|
|
143
|
+
currency: str | None = None
|
|
144
|
+
description: str | None = None
|
|
145
|
+
tracking_id: str | None = None
|
|
146
|
+
message: str | None = None
|
|
147
|
+
type: str | None = None
|
|
148
|
+
test: bool | None = None
|
|
149
|
+
payment: PaymentInfo | None = None
|
|
150
|
+
credit_card: CreditCardInfo | None = None
|
|
151
|
+
code: str | None = None
|
|
152
|
+
friendly_message: str | None = None
|
|
153
|
+
created_at: str | None = None
|
|
154
|
+
updated_at: str | None = None
|
|
155
|
+
paid_at: str | None = None
|
|
156
|
+
receipt_url: str | None = None
|
|
157
|
+
redirect_url: str | None = None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class CaptureRequest(CamelModel):
|
|
161
|
+
parent_uid: str
|
|
162
|
+
amount: int
|
|
163
|
+
tracking_id: str | None = None
|
|
164
|
+
additional_data: AdditionalData | None = None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class CaptureResponse(CamelModel):
|
|
168
|
+
uid: str
|
|
169
|
+
status: str | None = None
|
|
170
|
+
type: str | None = None
|
|
171
|
+
parent_uid: str | None = None
|
|
172
|
+
amount: int | None = None
|
|
173
|
+
currency: str | None = None
|
|
174
|
+
code: str | None = None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class VoidRequest(CamelModel):
|
|
178
|
+
parent_uid: str
|
|
179
|
+
amount: int
|
|
180
|
+
tracking_id: str | None = None
|
|
181
|
+
additional_data: AdditionalData | None = None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class VoidResponse(CamelModel):
|
|
185
|
+
uid: str
|
|
186
|
+
status: str | None = None
|
|
187
|
+
type: str | None = None
|
|
188
|
+
message: str | None = None
|
|
189
|
+
code: str | None = None
|
|
190
|
+
tracking_id: str | None = None
|
|
191
|
+
receipt_url: str | None = None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class RefundRequest(CamelModel):
|
|
195
|
+
parent_uid: str
|
|
196
|
+
amount: int
|
|
197
|
+
reason: str
|
|
198
|
+
tracking_id: str | None = None
|
|
199
|
+
additional_data: AdditionalData | None = None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class RefundResponse(CamelModel):
|
|
203
|
+
uid: str | None = None
|
|
204
|
+
parent_uid: str | None = None
|
|
205
|
+
type: str | None = None
|
|
206
|
+
status: str | None = None
|
|
207
|
+
message: str | None = None
|
|
208
|
+
amount: int | None = None
|
|
209
|
+
currency: str | None = None
|
|
210
|
+
created_at: str | None = None
|
|
211
|
+
reason: str | None = None
|
|
212
|
+
refund: dict[str, Any] | None = None
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# ── token API ─────────────────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class CreateTokenRequest(CamelModel):
|
|
219
|
+
number: str
|
|
220
|
+
holder: str
|
|
221
|
+
exp_month: str
|
|
222
|
+
exp_year: str
|
|
223
|
+
contract: list[str] | None = None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class TokenResponse(CamelModel):
|
|
227
|
+
holder: str | None = None
|
|
228
|
+
stamp: str | None = None
|
|
229
|
+
brand: str | None = None
|
|
230
|
+
last_4: str | None = None
|
|
231
|
+
first_1: str | None = None
|
|
232
|
+
token: str | None = None
|
|
233
|
+
exp_month: int | None = None
|
|
234
|
+
exp_year: int | None = None
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# ── checkout API ──────────────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class CheckoutSettings(CamelModel):
|
|
241
|
+
return_url: str | None = None
|
|
242
|
+
success_url: str | None = None
|
|
243
|
+
decline_url: str | None = None
|
|
244
|
+
fail_url: str | None = None
|
|
245
|
+
cancel_url: str | None = None
|
|
246
|
+
notification_url: str | None = None
|
|
247
|
+
button_next_text: str | None = None
|
|
248
|
+
auto_pay: bool | None = None
|
|
249
|
+
language: str | None = None
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class PaymentMethod(CamelModel):
|
|
253
|
+
types: list[str] | None = None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class CheckoutCreditCard(CamelModel):
|
|
257
|
+
token: str | None = None
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class CheckoutOrder(CamelModel):
|
|
261
|
+
currency: str
|
|
262
|
+
amount: int
|
|
263
|
+
description: str | None = None
|
|
264
|
+
tracking_id: str | None = None
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class CheckoutRequest(CamelModel):
|
|
268
|
+
test: bool | None = None
|
|
269
|
+
transaction_type: str = "payment"
|
|
270
|
+
attempts: int | None = None
|
|
271
|
+
iframe: bool | None = None
|
|
272
|
+
settings: CheckoutSettings | None = None
|
|
273
|
+
payment_method: PaymentMethod | None = None
|
|
274
|
+
credit_card: CheckoutCreditCard | None = None
|
|
275
|
+
order: CheckoutOrder
|
|
276
|
+
customer: Customer | None = None
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class CheckoutResponse(CamelModel):
|
|
280
|
+
token: str
|
|
281
|
+
redirect_url: str | None = None
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class CheckoutStatus(CamelModel):
|
|
285
|
+
token: str | None = None
|
|
286
|
+
shop_id: int | None = None
|
|
287
|
+
transaction_type: str | None = None
|
|
288
|
+
gateway_response: dict[str, Any] | None = None
|
|
289
|
+
order: dict[str, Any] | None = None
|
|
290
|
+
settings: CheckoutSettings | None = None
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# ── direct / APM API ──────────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class ApmPaymentRequest(CamelModel):
|
|
297
|
+
amount: int
|
|
298
|
+
currency: str
|
|
299
|
+
description: str | None = None
|
|
300
|
+
email: str | None = None
|
|
301
|
+
ip: str | None = None
|
|
302
|
+
success_url: str | None = None
|
|
303
|
+
order_id: str | int | None = None
|
|
304
|
+
tracking_id: str | None = None
|
|
305
|
+
notification_url: str | None = None
|
|
306
|
+
expired_at: str | None = None
|
|
307
|
+
test: bool | None = None
|
|
308
|
+
language: str | None = None
|
|
309
|
+
return_url: str | None = None
|
|
310
|
+
customer: Customer | None = None
|
|
311
|
+
payment_method: dict[str, Any] = Field(alias="paymentMethod")
|
|
312
|
+
additional_data: dict[str, Any] | None = None
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class ApmPaymentResponse(CamelModel):
|
|
316
|
+
uid: str | None = None
|
|
317
|
+
status: str | None = None
|
|
318
|
+
type: str | None = None
|
|
319
|
+
amount: int | None = None
|
|
320
|
+
currency: str | None = None
|
|
321
|
+
message: str | None = None
|
|
322
|
+
tracking_id: str | None = None
|
|
323
|
+
test: bool | None = None
|
|
324
|
+
method_type: str | None = None
|
|
325
|
+
receipt_url: str | None = None
|
|
326
|
+
payment: PaymentInfo | None = None
|
|
327
|
+
created_at: str | None = None
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class ApmRefundRequest(CamelModel):
|
|
331
|
+
parent_uid: str
|
|
332
|
+
reason: str
|
|
333
|
+
amount: int | None = None
|
|
334
|
+
tracking_id: str | None = None
|
|
335
|
+
additional_data: dict[str, Any] | None = None
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class ApmRefundResponse(CamelModel):
|
|
339
|
+
uid: str | None = None
|
|
340
|
+
parent_uid: str | None = None
|
|
341
|
+
type: str | None = None
|
|
342
|
+
status: str | None = None
|
|
343
|
+
message: str | None = None
|
|
344
|
+
amount: int | None = None
|
|
345
|
+
currency: str | None = None
|
|
346
|
+
refund: dict[str, Any] | None = None
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# ── webhook ───────────────────────────────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class WebhookNotification(CamelModel):
|
|
353
|
+
transaction: WebhookTransaction
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class WebhookTransaction(CamelModel):
|
|
357
|
+
uid: str
|
|
358
|
+
status: str
|
|
359
|
+
type: str | None = None
|
|
360
|
+
amount: int | None = None
|
|
361
|
+
currency: str | None = None
|
|
362
|
+
description: str | None = None
|
|
363
|
+
created_at: str | None = None
|
|
364
|
+
method_type: str | None = None
|
|
365
|
+
payment: PaymentInfo | None = None
|
|
366
|
+
message: str | None = None
|
|
367
|
+
tracking_id: str | None = None
|
|
368
|
+
test: bool | None = None
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
WebhookNotification.model_rebuild()
|