paybridge-np 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.
- paybridge_np-0.1.0/.gitignore +10 -0
- paybridge_np-0.1.0/LICENSE +21 -0
- paybridge_np-0.1.0/PKG-INFO +163 -0
- paybridge_np-0.1.0/README.md +136 -0
- paybridge_np-0.1.0/pyproject.toml +48 -0
- paybridge_np-0.1.0/src/paybridge_np/__init__.py +26 -0
- paybridge_np-0.1.0/src/paybridge_np/client.py +112 -0
- paybridge_np-0.1.0/src/paybridge_np/errors.py +74 -0
- paybridge_np-0.1.0/src/paybridge_np/http.py +89 -0
- paybridge_np-0.1.0/src/paybridge_np/py.typed +0 -0
- paybridge_np-0.1.0/src/paybridge_np/resources/__init__.py +21 -0
- paybridge_np-0.1.0/src/paybridge_np/resources/checkout.py +25 -0
- paybridge_np-0.1.0/src/paybridge_np/resources/customers.py +48 -0
- paybridge_np-0.1.0/src/paybridge_np/resources/invoices.py +44 -0
- paybridge_np-0.1.0/src/paybridge_np/resources/payments.py +31 -0
- paybridge_np-0.1.0/src/paybridge_np/resources/plans.py +44 -0
- paybridge_np-0.1.0/src/paybridge_np/resources/refunds.py +44 -0
- paybridge_np-0.1.0/src/paybridge_np/resources/subscriptions.py +84 -0
- paybridge_np-0.1.0/src/paybridge_np/resources/webhooks.py +120 -0
- paybridge_np-0.1.0/src/paybridge_np/types.py +177 -0
- paybridge_np-0.1.0/tests/__init__.py +0 -0
- paybridge_np-0.1.0/tests/test_client.py +42 -0
- paybridge_np-0.1.0/tests/test_errors.py +52 -0
- paybridge_np-0.1.0/tests/test_webhooks.py +66 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PayBridgeNP
|
|
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,163 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: paybridge-np
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the PayBridgeNP payment gateway - accept eSewa, Khalti, and ConnectIPS through a single API
|
|
5
|
+
Project-URL: Homepage, https://paybridgenp.com
|
|
6
|
+
Project-URL: Documentation, https://docs.paybridgenp.com
|
|
7
|
+
Project-URL: Repository, https://github.com/paybridgenp/paybridgenp-python-sdk
|
|
8
|
+
Project-URL: Issues, https://github.com/paybridgenp/paybridgenp-python-sdk/issues
|
|
9
|
+
Author-email: PayBridgeNP <support@paybridgenp.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: connectips,esewa,khalti,nepal,nepal-payments,paybridge,payment-gateway,payments
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: httpx>=0.24.0
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# paybridge-np
|
|
29
|
+
|
|
30
|
+
Official Python SDK for the [PayBridgeNP](https://paybridgenp.com) payment gateway. Accept eSewa, Khalti, and ConnectIPS through a single API.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install paybridge-np
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from paybridge_np import PayBridge
|
|
42
|
+
|
|
43
|
+
client = PayBridge(api_key="sk_live_...") # from dashboard.paybridgenp.com
|
|
44
|
+
|
|
45
|
+
# Create a checkout session
|
|
46
|
+
session = client.checkout.create({
|
|
47
|
+
"amount": 250000, # NPR 2,500 in paisa
|
|
48
|
+
"currency": "NPR",
|
|
49
|
+
"returnUrl": "https://mystore.com/success",
|
|
50
|
+
"cancelUrl": "https://mystore.com/cart",
|
|
51
|
+
"metadata": {"orderId": "ORD-7842"},
|
|
52
|
+
"customer": {
|
|
53
|
+
"name": "Ram Shrestha",
|
|
54
|
+
"email": "ram@example.com",
|
|
55
|
+
"phone": "9841000000",
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
# Redirect customer to hosted checkout
|
|
60
|
+
# session["checkout_url"] => https://paybridgenp.com/checkout/cs_xxx
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Payments
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
# List payments
|
|
67
|
+
result = client.payments.list(limit=20)
|
|
68
|
+
payments = result["data"]
|
|
69
|
+
|
|
70
|
+
# Get a single payment
|
|
71
|
+
payment = client.payments.retrieve("pay_xxx")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Refunds
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
refund = client.refunds.create({
|
|
78
|
+
"paymentId": "pay_xxx",
|
|
79
|
+
"amount": 100000, # NPR 1,000 in paisa
|
|
80
|
+
"reason": "customer_request",
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Webhooks
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# Register an endpoint
|
|
88
|
+
endpoint = client.webhooks.create(
|
|
89
|
+
url="https://mystore.com/webhooks/paybridge",
|
|
90
|
+
events=["payment.succeeded", "payment.failed"],
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Verify a webhook signature (no client instance needed)
|
|
94
|
+
from paybridge_np.resources.webhooks import WebhooksResource
|
|
95
|
+
|
|
96
|
+
event = WebhooksResource.construct_event(
|
|
97
|
+
body=raw_body,
|
|
98
|
+
signature=request.headers["X-PayBridge-Signature"],
|
|
99
|
+
secret="whsec_...",
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Billing (Subscriptions)
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# Create a plan
|
|
107
|
+
plan = client.plans.create({
|
|
108
|
+
"name": "Pro Monthly",
|
|
109
|
+
"amount": 99900,
|
|
110
|
+
"intervalUnit": "month",
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
# Create a customer
|
|
114
|
+
customer = client.customers.create({
|
|
115
|
+
"name": "Sita Gurung",
|
|
116
|
+
"email": "sita@example.com",
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
# Subscribe
|
|
120
|
+
subscription = client.subscriptions.create({
|
|
121
|
+
"customerId": customer["id"],
|
|
122
|
+
"planId": plan["id"],
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
# List invoices
|
|
126
|
+
invoices = client.invoices.list(customer_id=customer["id"])
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Sandbox mode
|
|
130
|
+
|
|
131
|
+
Use a sandbox API key (`sk_sandbox_...`) to test without real money. The SDK automatically routes to sandbox endpoints.
|
|
132
|
+
|
|
133
|
+
## Error handling
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from paybridge_np import PayBridgeError, AuthenticationError
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
session = client.checkout.create({...})
|
|
140
|
+
except AuthenticationError:
|
|
141
|
+
print("Invalid API key")
|
|
142
|
+
except PayBridgeError as e:
|
|
143
|
+
print(e, e.status_code)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Context manager
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
with PayBridge(api_key="sk_live_...") as client:
|
|
150
|
+
session = client.checkout.create({...})
|
|
151
|
+
# HTTP client is closed automatically
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Documentation
|
|
155
|
+
|
|
156
|
+
- [API Reference](https://docs.paybridgenp.com)
|
|
157
|
+
- [Dashboard](https://dashboard.paybridgenp.com)
|
|
158
|
+
- [Guides](https://docs.paybridgenp.com/guides/sandbox-testing)
|
|
159
|
+
- [Discord](https://discord.gg/aquta4JwJt)
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# paybridge-np
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [PayBridgeNP](https://paybridgenp.com) payment gateway. Accept eSewa, Khalti, and ConnectIPS through a single API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install paybridge-np
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from paybridge_np import PayBridge
|
|
15
|
+
|
|
16
|
+
client = PayBridge(api_key="sk_live_...") # from dashboard.paybridgenp.com
|
|
17
|
+
|
|
18
|
+
# Create a checkout session
|
|
19
|
+
session = client.checkout.create({
|
|
20
|
+
"amount": 250000, # NPR 2,500 in paisa
|
|
21
|
+
"currency": "NPR",
|
|
22
|
+
"returnUrl": "https://mystore.com/success",
|
|
23
|
+
"cancelUrl": "https://mystore.com/cart",
|
|
24
|
+
"metadata": {"orderId": "ORD-7842"},
|
|
25
|
+
"customer": {
|
|
26
|
+
"name": "Ram Shrestha",
|
|
27
|
+
"email": "ram@example.com",
|
|
28
|
+
"phone": "9841000000",
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
# Redirect customer to hosted checkout
|
|
33
|
+
# session["checkout_url"] => https://paybridgenp.com/checkout/cs_xxx
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Payments
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# List payments
|
|
40
|
+
result = client.payments.list(limit=20)
|
|
41
|
+
payments = result["data"]
|
|
42
|
+
|
|
43
|
+
# Get a single payment
|
|
44
|
+
payment = client.payments.retrieve("pay_xxx")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Refunds
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
refund = client.refunds.create({
|
|
51
|
+
"paymentId": "pay_xxx",
|
|
52
|
+
"amount": 100000, # NPR 1,000 in paisa
|
|
53
|
+
"reason": "customer_request",
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Webhooks
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
# Register an endpoint
|
|
61
|
+
endpoint = client.webhooks.create(
|
|
62
|
+
url="https://mystore.com/webhooks/paybridge",
|
|
63
|
+
events=["payment.succeeded", "payment.failed"],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Verify a webhook signature (no client instance needed)
|
|
67
|
+
from paybridge_np.resources.webhooks import WebhooksResource
|
|
68
|
+
|
|
69
|
+
event = WebhooksResource.construct_event(
|
|
70
|
+
body=raw_body,
|
|
71
|
+
signature=request.headers["X-PayBridge-Signature"],
|
|
72
|
+
secret="whsec_...",
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Billing (Subscriptions)
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
# Create a plan
|
|
80
|
+
plan = client.plans.create({
|
|
81
|
+
"name": "Pro Monthly",
|
|
82
|
+
"amount": 99900,
|
|
83
|
+
"intervalUnit": "month",
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
# Create a customer
|
|
87
|
+
customer = client.customers.create({
|
|
88
|
+
"name": "Sita Gurung",
|
|
89
|
+
"email": "sita@example.com",
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
# Subscribe
|
|
93
|
+
subscription = client.subscriptions.create({
|
|
94
|
+
"customerId": customer["id"],
|
|
95
|
+
"planId": plan["id"],
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
# List invoices
|
|
99
|
+
invoices = client.invoices.list(customer_id=customer["id"])
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Sandbox mode
|
|
103
|
+
|
|
104
|
+
Use a sandbox API key (`sk_sandbox_...`) to test without real money. The SDK automatically routes to sandbox endpoints.
|
|
105
|
+
|
|
106
|
+
## Error handling
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from paybridge_np import PayBridgeError, AuthenticationError
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
session = client.checkout.create({...})
|
|
113
|
+
except AuthenticationError:
|
|
114
|
+
print("Invalid API key")
|
|
115
|
+
except PayBridgeError as e:
|
|
116
|
+
print(e, e.status_code)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Context manager
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
with PayBridge(api_key="sk_live_...") as client:
|
|
123
|
+
session = client.checkout.create({...})
|
|
124
|
+
# HTTP client is closed automatically
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Documentation
|
|
128
|
+
|
|
129
|
+
- [API Reference](https://docs.paybridgenp.com)
|
|
130
|
+
- [Dashboard](https://dashboard.paybridgenp.com)
|
|
131
|
+
- [Guides](https://docs.paybridgenp.com/guides/sandbox-testing)
|
|
132
|
+
- [Discord](https://discord.gg/aquta4JwJt)
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling<1.22"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "paybridge-np"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the PayBridgeNP payment gateway - accept eSewa, Khalti, and ConnectIPS through a single API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "PayBridgeNP", email = "support@paybridgenp.com" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"paybridge",
|
|
15
|
+
"nepal",
|
|
16
|
+
"payments",
|
|
17
|
+
"esewa",
|
|
18
|
+
"khalti",
|
|
19
|
+
"connectips",
|
|
20
|
+
"payment-gateway",
|
|
21
|
+
"nepal-payments",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.9",
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Programming Language :: Python :: 3.13",
|
|
33
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
34
|
+
"Typing :: Typed",
|
|
35
|
+
]
|
|
36
|
+
dependencies = ["httpx>=0.24.0"]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://paybridgenp.com"
|
|
40
|
+
Documentation = "https://docs.paybridgenp.com"
|
|
41
|
+
Repository = "https://github.com/paybridgenp/paybridgenp-python-sdk"
|
|
42
|
+
Issues = "https://github.com/paybridgenp/paybridgenp-python-sdk/issues"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["src/paybridge_np"]
|
|
46
|
+
|
|
47
|
+
[tool.pytest.ini_options]
|
|
48
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Official Python SDK for the PayBridgeNP payment gateway."""
|
|
2
|
+
|
|
3
|
+
from .client import PayBridge
|
|
4
|
+
from .errors import (
|
|
5
|
+
PayBridgeError,
|
|
6
|
+
AuthenticationError,
|
|
7
|
+
InvalidRequestError,
|
|
8
|
+
NotFoundError,
|
|
9
|
+
RateLimitError,
|
|
10
|
+
ConnectionError as PayBridgeConnectionError,
|
|
11
|
+
SignatureVerificationError,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
SDK_VERSION = "0.1.0"
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"PayBridge",
|
|
18
|
+
"PayBridgeError",
|
|
19
|
+
"AuthenticationError",
|
|
20
|
+
"InvalidRequestError",
|
|
21
|
+
"NotFoundError",
|
|
22
|
+
"RateLimitError",
|
|
23
|
+
"PayBridgeConnectionError",
|
|
24
|
+
"SignatureVerificationError",
|
|
25
|
+
"SDK_VERSION",
|
|
26
|
+
]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Main PayBridge client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .http import HttpClient
|
|
6
|
+
from .resources.checkout import CheckoutResource
|
|
7
|
+
from .resources.payments import PaymentsResource
|
|
8
|
+
from .resources.refunds import RefundsResource
|
|
9
|
+
from .resources.webhooks import WebhooksResource
|
|
10
|
+
from .resources.plans import PlansResource
|
|
11
|
+
from .resources.customers import CustomersResource
|
|
12
|
+
from .resources.subscriptions import SubscriptionsResource
|
|
13
|
+
from .resources.invoices import InvoicesResource
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PayBridge:
|
|
17
|
+
"""PayBridgeNP API client.
|
|
18
|
+
|
|
19
|
+
Usage::
|
|
20
|
+
|
|
21
|
+
from paybridge_np import PayBridge
|
|
22
|
+
|
|
23
|
+
client = PayBridge(api_key="sk_live_...")
|
|
24
|
+
session = client.checkout.create({
|
|
25
|
+
"amount": 250000,
|
|
26
|
+
"returnUrl": "https://mystore.com/success",
|
|
27
|
+
})
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# Static webhook utility -- no instance required for signature verification.
|
|
31
|
+
webhooks_static = WebhooksResource()
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
api_key: str,
|
|
36
|
+
*,
|
|
37
|
+
base_url: str | None = None,
|
|
38
|
+
timeout: float | None = None,
|
|
39
|
+
max_retries: int | None = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
self._http = HttpClient(
|
|
42
|
+
api_key=api_key,
|
|
43
|
+
base_url=base_url,
|
|
44
|
+
timeout=timeout,
|
|
45
|
+
max_retries=max_retries,
|
|
46
|
+
)
|
|
47
|
+
self._checkout: CheckoutResource | None = None
|
|
48
|
+
self._payments: PaymentsResource | None = None
|
|
49
|
+
self._refunds: RefundsResource | None = None
|
|
50
|
+
self._webhooks: WebhooksResource | None = None
|
|
51
|
+
self._plans: PlansResource | None = None
|
|
52
|
+
self._customers: CustomersResource | None = None
|
|
53
|
+
self._subscriptions: SubscriptionsResource | None = None
|
|
54
|
+
self._invoices: InvoicesResource | None = None
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def checkout(self) -> CheckoutResource:
|
|
58
|
+
if self._checkout is None:
|
|
59
|
+
self._checkout = CheckoutResource(self._http)
|
|
60
|
+
return self._checkout
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def payments(self) -> PaymentsResource:
|
|
64
|
+
if self._payments is None:
|
|
65
|
+
self._payments = PaymentsResource(self._http)
|
|
66
|
+
return self._payments
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def refunds(self) -> RefundsResource:
|
|
70
|
+
if self._refunds is None:
|
|
71
|
+
self._refunds = RefundsResource(self._http)
|
|
72
|
+
return self._refunds
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def webhooks(self) -> WebhooksResource:
|
|
76
|
+
if self._webhooks is None:
|
|
77
|
+
self._webhooks = WebhooksResource(self._http)
|
|
78
|
+
return self._webhooks
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def plans(self) -> PlansResource:
|
|
82
|
+
if self._plans is None:
|
|
83
|
+
self._plans = PlansResource(self._http)
|
|
84
|
+
return self._plans
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def customers(self) -> CustomersResource:
|
|
88
|
+
if self._customers is None:
|
|
89
|
+
self._customers = CustomersResource(self._http)
|
|
90
|
+
return self._customers
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def subscriptions(self) -> SubscriptionsResource:
|
|
94
|
+
if self._subscriptions is None:
|
|
95
|
+
self._subscriptions = SubscriptionsResource(self._http)
|
|
96
|
+
return self._subscriptions
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def invoices(self) -> InvoicesResource:
|
|
100
|
+
if self._invoices is None:
|
|
101
|
+
self._invoices = InvoicesResource(self._http)
|
|
102
|
+
return self._invoices
|
|
103
|
+
|
|
104
|
+
def close(self) -> None:
|
|
105
|
+
"""Close the underlying HTTP client."""
|
|
106
|
+
self._http.close()
|
|
107
|
+
|
|
108
|
+
def __enter__(self) -> PayBridge:
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
def __exit__(self, *args: object) -> None:
|
|
112
|
+
self.close()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""PayBridgeNP SDK error types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PayBridgeError(Exception):
|
|
9
|
+
"""Base error for all PayBridgeNP API errors."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
message: str,
|
|
14
|
+
status_code: int = 0,
|
|
15
|
+
code: str = "api_error",
|
|
16
|
+
raw: dict[str, Any] | None = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
super().__init__(message)
|
|
19
|
+
self.status_code = status_code
|
|
20
|
+
self.code = code
|
|
21
|
+
self.raw = raw
|
|
22
|
+
|
|
23
|
+
def to_dict(self) -> dict[str, Any]:
|
|
24
|
+
return {
|
|
25
|
+
"name": type(self).__name__,
|
|
26
|
+
"message": str(self),
|
|
27
|
+
"code": self.code,
|
|
28
|
+
"status_code": self.status_code,
|
|
29
|
+
"raw": self.raw,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AuthenticationError(PayBridgeError):
|
|
34
|
+
def __init__(self, message: str, raw: dict[str, Any] | None = None) -> None:
|
|
35
|
+
super().__init__(message, 401, "authentication_error", raw)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class InvalidRequestError(PayBridgeError):
|
|
39
|
+
def __init__(self, message: str, raw: dict[str, Any] | None = None) -> None:
|
|
40
|
+
super().__init__(message, 400, "invalid_request_error", raw)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class NotFoundError(PayBridgeError):
|
|
44
|
+
def __init__(self, message: str, raw: dict[str, Any] | None = None) -> None:
|
|
45
|
+
super().__init__(message, 404, "not_found_error", raw)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class RateLimitError(PayBridgeError):
|
|
49
|
+
def __init__(self, message: str, raw: dict[str, Any] | None = None) -> None:
|
|
50
|
+
super().__init__(message, 429, "rate_limit_error", raw)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ConnectionError(PayBridgeError):
|
|
54
|
+
def __init__(self, message: str) -> None:
|
|
55
|
+
super().__init__(f"Connection error: {message}", 0, "connection_error")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SignatureVerificationError(PayBridgeError):
|
|
59
|
+
def __init__(self, message: str = "Webhook signature verification failed") -> None:
|
|
60
|
+
super().__init__(message, 0, "signature_verification_error")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def create_error(
|
|
64
|
+
message: str, status_code: int, raw: dict[str, Any] | None
|
|
65
|
+
) -> PayBridgeError:
|
|
66
|
+
if status_code == 401:
|
|
67
|
+
return AuthenticationError(message, raw)
|
|
68
|
+
if status_code == 404:
|
|
69
|
+
return NotFoundError(message, raw)
|
|
70
|
+
if status_code in (400, 422):
|
|
71
|
+
return InvalidRequestError(message, raw)
|
|
72
|
+
if status_code == 429:
|
|
73
|
+
return RateLimitError(message, raw)
|
|
74
|
+
return PayBridgeError(message, status_code, "api_error", raw)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""HTTP client with retries and exponential backoff."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
import time
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from .errors import PayBridgeError, ConnectionError, create_error
|
|
12
|
+
|
|
13
|
+
DEFAULT_BASE_URL = "https://api.paybridgenp.com"
|
|
14
|
+
DEFAULT_TIMEOUT = 30.0
|
|
15
|
+
DEFAULT_MAX_RETRIES = 2
|
|
16
|
+
RETRY_STATUSES = {500, 502, 503, 504}
|
|
17
|
+
INITIAL_BACKOFF_S = 0.5
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _backoff(attempt: int) -> float:
|
|
21
|
+
return INITIAL_BACKOFF_S * (2 ** (attempt - 1)) + random.random() * 0.1
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HttpClient:
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
api_key: str,
|
|
28
|
+
base_url: str | None = None,
|
|
29
|
+
timeout: float | None = None,
|
|
30
|
+
max_retries: int | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
self._base_url = (base_url or DEFAULT_BASE_URL).rstrip("/")
|
|
33
|
+
self._api_key = api_key
|
|
34
|
+
self._timeout = timeout or DEFAULT_TIMEOUT
|
|
35
|
+
self._max_retries = max_retries if max_retries is not None else DEFAULT_MAX_RETRIES
|
|
36
|
+
self._client = httpx.Client(
|
|
37
|
+
base_url=self._base_url,
|
|
38
|
+
timeout=self._timeout,
|
|
39
|
+
headers={
|
|
40
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
"User-Agent": "PayBridgeNP-Python/0.1.0",
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def request(self, method: str, path: str, json: Any = None) -> Any:
|
|
47
|
+
attempt = 0
|
|
48
|
+
while True:
|
|
49
|
+
attempt += 1
|
|
50
|
+
try:
|
|
51
|
+
resp = self._client.request(method, path, json=json)
|
|
52
|
+
except httpx.HTTPError as exc:
|
|
53
|
+
if attempt > self._max_retries:
|
|
54
|
+
raise ConnectionError(str(exc)) from exc
|
|
55
|
+
time.sleep(_backoff(attempt))
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
if resp.is_success:
|
|
59
|
+
return resp.json()
|
|
60
|
+
|
|
61
|
+
if resp.status_code in RETRY_STATUSES and attempt <= self._max_retries:
|
|
62
|
+
retry_after = resp.headers.get("Retry-After")
|
|
63
|
+
delay = float(retry_after) if retry_after else _backoff(attempt)
|
|
64
|
+
time.sleep(delay)
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
raw: dict[str, Any] | None = None
|
|
68
|
+
try:
|
|
69
|
+
raw = resp.json()
|
|
70
|
+
except Exception:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
message = raw.get("error", f"HTTP {resp.status_code}") if raw and isinstance(raw.get("error"), str) else f"HTTP {resp.status_code}"
|
|
74
|
+
raise create_error(message, resp.status_code, raw)
|
|
75
|
+
|
|
76
|
+
def get(self, path: str) -> Any:
|
|
77
|
+
return self.request("GET", path)
|
|
78
|
+
|
|
79
|
+
def post(self, path: str, json: Any) -> Any:
|
|
80
|
+
return self.request("POST", path, json=json)
|
|
81
|
+
|
|
82
|
+
def patch(self, path: str, json: Any) -> Any:
|
|
83
|
+
return self.request("PATCH", path, json=json)
|
|
84
|
+
|
|
85
|
+
def delete(self, path: str) -> Any:
|
|
86
|
+
return self.request("DELETE", path)
|
|
87
|
+
|
|
88
|
+
def close(self) -> None:
|
|
89
|
+
self._client.close()
|
|
File without changes
|