sebpay 0.2.0__py3-none-any.whl
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.
- sebpay/__init__.py +6 -0
- sebpay/_version.py +2 -0
- sebpay/client.py +69 -0
- sebpay/errors.py +17 -0
- sebpay/py.typed +0 -0
- sebpay/resources/__init__.py +0 -0
- sebpay/resources/checkout_sessions.py +30 -0
- sebpay/types.py +46 -0
- sebpay/webhooks.py +22 -0
- sebpay-0.2.0.dist-info/METADATA +143 -0
- sebpay-0.2.0.dist-info/RECORD +14 -0
- sebpay-0.2.0.dist-info/WHEEL +5 -0
- sebpay-0.2.0.dist-info/licenses/LICENSE +21 -0
- sebpay-0.2.0.dist-info/top_level.txt +1 -0
sebpay/__init__.py
ADDED
sebpay/_version.py
ADDED
sebpay/client.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from ._version import __version__
|
|
8
|
+
from .errors import SebpayApiError
|
|
9
|
+
from .resources.checkout_sessions import CheckoutSessions
|
|
10
|
+
|
|
11
|
+
LIVE_BASE_URL = "https://newapi.sebpay.africa/api/v1"
|
|
12
|
+
SANDBOX_BASE_URL = "https://api.sandbox.sebpay.bj/api/v1"
|
|
13
|
+
DEFAULT_TIMEOUT = 30
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SebpayClient:
|
|
17
|
+
"""Entry point for Sebpay hosted checkout. Pass the key pair from your
|
|
18
|
+
dashboard (API développeur): the sandbox and live are separate servers,
|
|
19
|
+
and pk_test_ keys are sent to the sandbox, pk_live_ keys to live.
|
|
20
|
+
`base_url` overrides that choice; `timeout` is in seconds.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, public_key: str, secret_key: str, base_url: Optional[str] = None, timeout: float = DEFAULT_TIMEOUT):
|
|
24
|
+
self._public_key = public_key
|
|
25
|
+
self._secret_key = secret_key
|
|
26
|
+
self._timeout = timeout
|
|
27
|
+
self.base_url = (base_url or (SANDBOX_BASE_URL if public_key.startswith("pk_test_") else LIVE_BASE_URL)).rstrip("/")
|
|
28
|
+
self._session = requests.Session()
|
|
29
|
+
|
|
30
|
+
# The transport stays private: the SDK only calls the endpoints it wraps
|
|
31
|
+
self.checkout_sessions = CheckoutSessions(self._request)
|
|
32
|
+
|
|
33
|
+
def _request(self, method: str, path: str, body: Optional[dict] = None) -> Any:
|
|
34
|
+
"""Returns the `data` field of a successful response; raises SebpayApiError otherwise."""
|
|
35
|
+
headers = {
|
|
36
|
+
"X-Public-Key": self._public_key,
|
|
37
|
+
"X-Secret-Key": self._secret_key,
|
|
38
|
+
"Accept": "application/json",
|
|
39
|
+
"User-Agent": f"sebpay-python/{__version__}",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
response = self._session.request(method, f"{self.base_url}{path}", headers=headers, json=body, timeout=self._timeout)
|
|
44
|
+
except requests.Timeout as e:
|
|
45
|
+
raise SebpayApiError(f"Sebpay request timed out after {self._timeout} s", 0, None, code="TIMEOUT") from e
|
|
46
|
+
except requests.RequestException as e:
|
|
47
|
+
raise SebpayApiError(f"Sebpay request failed: {e}", 0, None, code="NETWORK_ERROR") from e
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
decoded = response.json()
|
|
51
|
+
except ValueError:
|
|
52
|
+
decoded = None
|
|
53
|
+
error_body = decoded if decoded is not None else response.text
|
|
54
|
+
|
|
55
|
+
if not 200 <= response.status_code < 300 or (isinstance(decoded, dict) and decoded.get("success") is False):
|
|
56
|
+
errors = decoded.get("errors") if isinstance(decoded, dict) else None
|
|
57
|
+
code = errors.get("code") if isinstance(errors, dict) else None
|
|
58
|
+
message = decoded.get("message") if isinstance(decoded, dict) else None
|
|
59
|
+
raise SebpayApiError(
|
|
60
|
+
message if isinstance(message, str) else f"Sebpay API error (HTTP {response.status_code})",
|
|
61
|
+
response.status_code,
|
|
62
|
+
error_body,
|
|
63
|
+
code=code if isinstance(code, str) else None,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if not isinstance(decoded, dict) or not isinstance(decoded.get("data"), dict):
|
|
67
|
+
raise SebpayApiError("Sebpay API returned an unexpected response", response.status_code, error_body, code="INVALID_RESPONSE")
|
|
68
|
+
|
|
69
|
+
return decoded["data"]
|
sebpay/errors.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SebpayApiError(Exception):
|
|
5
|
+
"""Raised for every failed call.
|
|
6
|
+
|
|
7
|
+
status: the HTTP status, or 0 when no response came back (code "NETWORK_ERROR" or "TIMEOUT").
|
|
8
|
+
code: the API's errors.code when it sends one ("EXTERNAL_REFERENCE_CONFLICT", "RATE_LIMITED",
|
|
9
|
+
"WRONG_ENVIRONMENT"…), else None.
|
|
10
|
+
body: the decoded response (its text when it was not JSON).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, message: str, status: int, body: Any = None, code: Optional[str] = None):
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
self.status = status
|
|
16
|
+
self.code = code
|
|
17
|
+
self.body = body
|
sebpay/py.typed
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
4
|
+
from urllib.parse import quote
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..types import CheckoutSession, CheckoutSessionDetails
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CheckoutSessions:
|
|
11
|
+
def __init__(self, request: Callable[..., Any]):
|
|
12
|
+
self._request = request
|
|
13
|
+
|
|
14
|
+
def create(self, **params: Any) -> CheckoutSession:
|
|
15
|
+
"""Create a hosted checkout session. Returns `checkout_url` — redirect
|
|
16
|
+
your customer's browser there.
|
|
17
|
+
|
|
18
|
+
Params: amount, currency, redirect_url (required), external_reference,
|
|
19
|
+
callback_url, customer_name, customer_email, customer_phone, metadata (optional).
|
|
20
|
+
Sending an external_reference again with the same parameters returns the
|
|
21
|
+
same session with `idempotent_replay` = True.
|
|
22
|
+
"""
|
|
23
|
+
return self._request("POST", "/checkout/sessions", params)
|
|
24
|
+
|
|
25
|
+
def retrieve(self, id_or_reference: str) -> CheckoutSessionDetails:
|
|
26
|
+
"""Retrieve a session by its id (the `checkout_session_id` of your
|
|
27
|
+
redirect_url), its token or your external_reference. This is the
|
|
28
|
+
server-to-server confirmation: when your customer comes back, deliver
|
|
29
|
+
only if `transaction_status` is "approved" — the redirect alone proves nothing."""
|
|
30
|
+
return self._request("GET", f"/checkout/sessions/{quote(id_or_reference, safe='')}")
|
sebpay/types.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Shapes of what the API returns, for type checkers and editors — plain dicts at runtime."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Literal, Optional, TypedDict
|
|
4
|
+
|
|
5
|
+
CheckoutSessionStatus = Literal["open", "completed", "expired", "canceled"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _CheckoutSessionFields(TypedDict):
|
|
9
|
+
id: str
|
|
10
|
+
status: CheckoutSessionStatus
|
|
11
|
+
checkout_url: str
|
|
12
|
+
amount: float
|
|
13
|
+
currency: str
|
|
14
|
+
external_reference: Optional[str]
|
|
15
|
+
metadata: Optional[Dict[str, Any]]
|
|
16
|
+
expires_at: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CheckoutSession(_CheckoutSessionFields, total=False):
|
|
20
|
+
# present (True) when this external_reference had already created the session
|
|
21
|
+
idempotent_replay: bool
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CheckoutSessionDetails(_CheckoutSessionFields):
|
|
25
|
+
transaction_id: Optional[str]
|
|
26
|
+
# "approved" is the only status that means paid
|
|
27
|
+
transaction_status: Optional[Literal["pending", "approved", "rejected"]]
|
|
28
|
+
created_at: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _WebhookPayloadFields(TypedDict):
|
|
32
|
+
transaction_id: str
|
|
33
|
+
external_reference: Optional[str]
|
|
34
|
+
# only final statuses are sent
|
|
35
|
+
status: Literal["approved", "rejected"]
|
|
36
|
+
amount: float
|
|
37
|
+
currency: str
|
|
38
|
+
customer_phone: Optional[str]
|
|
39
|
+
created_at: str
|
|
40
|
+
updated_at: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class WebhookPayload(_WebhookPayloadFields, total=False):
|
|
44
|
+
# a hosted checkout's webhook names its session and echoes its metadata
|
|
45
|
+
checkout_session_id: str
|
|
46
|
+
metadata: Optional[Dict[str, Any]]
|
sebpay/webhooks.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import hmac
|
|
3
|
+
from typing import Optional, Union
|
|
4
|
+
|
|
5
|
+
SIGNATURE_HEADER = "X-SebPay-Signature"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def verify_signature(raw_body: Union[bytes, str], signature: Optional[str], secret_key: str) -> bool:
|
|
9
|
+
"""Verify the `X-SebPay-Signature` header Sebpay sends with every webhook
|
|
10
|
+
callback. Always pass the raw request body — re-serializing a parsed
|
|
11
|
+
payload (e.g. via json.dumps after parsing) can reorder keys and break
|
|
12
|
+
the signature. A missing signature returns False.
|
|
13
|
+
"""
|
|
14
|
+
if not isinstance(signature, str) or not signature:
|
|
15
|
+
return False
|
|
16
|
+
if isinstance(raw_body, str):
|
|
17
|
+
raw_body = raw_body.encode("utf-8")
|
|
18
|
+
if not isinstance(raw_body, (bytes, bytearray)):
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
expected = hmac.new(secret_key.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
|
|
22
|
+
return hmac.compare_digest(expected.encode("ascii"), signature.encode("utf-8"))
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sebpay
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official Python SDK for Sebpay hosted checkout — create a checkout session, redirect your customer, confirm the payment and verify webhooks.
|
|
5
|
+
Author: Sebpay
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://sebpay.africa
|
|
8
|
+
Project-URL: Repository, https://github.com/mjims/sebpay-sdk-py
|
|
9
|
+
Project-URL: Issues, https://github.com/mjims/sebpay-sdk-py/issues
|
|
10
|
+
Keywords: sebpay,payment,payments,checkout,mobile-money,africa,webhook,sdk
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: requests>=2.28
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# sebpay-python
|
|
30
|
+
|
|
31
|
+
Official Python SDK for [Sebpay](https://sebpay.africa) **hosted checkout**. It does four things:
|
|
32
|
+
- create a checkout session;
|
|
33
|
+
- redirect your customer to the Sebpay payment page;
|
|
34
|
+
- confirm the payment when they come back;
|
|
35
|
+
- verify Sebpay's webhooks.
|
|
36
|
+
|
|
37
|
+
Its only dependency is `requests`. Direct collections, payouts and reference data are not part of the SDK. They remain available over the plain HTTP API.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install sebpay
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requires Python 3.9 or later. The package ships its type hints (`py.typed`).
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### 1. Create a session and redirect your customer
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import os
|
|
53
|
+
from sebpay import SebpayClient
|
|
54
|
+
|
|
55
|
+
# Keys from your dashboard (API développeur): pk_test_ keys go to the sandbox server, pk_live_ keys to live.
|
|
56
|
+
sebpay = SebpayClient(os.environ["SEBPAY_PUBLIC_KEY"], os.environ["SEBPAY_SECRET_KEY"])
|
|
57
|
+
|
|
58
|
+
session = sebpay.checkout_sessions.create(
|
|
59
|
+
amount=5000,
|
|
60
|
+
currency="XOF",
|
|
61
|
+
redirect_url="https://yoursite.com/orders/return",
|
|
62
|
+
external_reference=order.id, # your order id: sending it again returns the same session
|
|
63
|
+
callback_url="https://yoursite.com/webhook/sebpay",
|
|
64
|
+
metadata={"order_id": order.id},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return redirect(session["checkout_url"])
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 2. Confirm the payment when your customer comes back
|
|
71
|
+
|
|
72
|
+
Your `redirect_url` receives `?checkout_session_id=<id>`. Coming back proves nothing, so ask Sebpay before you deliver:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
session = sebpay.checkout_sessions.retrieve(request.args["checkout_session_id"])
|
|
76
|
+
|
|
77
|
+
if session["transaction_status"] == "approved":
|
|
78
|
+
... # Paid: deliver the order (session["external_reference"], session["metadata"]).
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`retrieve()` also accepts your `external_reference`.
|
|
82
|
+
|
|
83
|
+
### 3. Verify the webhook
|
|
84
|
+
|
|
85
|
+
Sebpay POSTs the final status (`approved` or `rejected`) to your `callback_url`. For a checkout, the request also carries `checkout_session_id` and your `metadata`.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from sebpay import SIGNATURE_HEADER, verify_signature
|
|
89
|
+
|
|
90
|
+
# With Flask:
|
|
91
|
+
if not verify_signature(request.get_data(), request.headers.get(SIGNATURE_HEADER), os.environ["SEBPAY_SECRET_KEY"]):
|
|
92
|
+
return {"error": "Invalid signature"}, 401
|
|
93
|
+
|
|
94
|
+
payload = request.get_json() # payload["status"]: "approved" | "rejected"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- Always pass the **raw** request body. Re-serializing a parsed payload can reorder keys and break the signature.
|
|
98
|
+
- A missing header returns `False`.
|
|
99
|
+
|
|
100
|
+
The shapes of the session and of the webhook payload are described as `TypedDict`s in `sebpay.types`, for editors and type checkers.
|
|
101
|
+
|
|
102
|
+
## Options
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
SebpayClient(public_key, secret_key,
|
|
106
|
+
base_url="http://localhost:8000/api/v1", # only to test against a local API; otherwise chosen from the keys
|
|
107
|
+
timeout=10) # seconds, default 30
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`sebpay.base_url` tells which server the client talks to.
|
|
111
|
+
|
|
112
|
+
## Errors
|
|
113
|
+
|
|
114
|
+
Every failed call raises a `SebpayApiError`, with three attributes:
|
|
115
|
+
- `status`: the HTTP status, or 0 when no response came back;
|
|
116
|
+
- `code`: the API's `errors.code`, or one of the SDK's own codes. Examples: `EXTERNAL_REFERENCE_CONFLICT`, `RATE_LIMITED`, `WRONG_ENVIRONMENT`, `NETWORK_ERROR`, `TIMEOUT`, `INVALID_RESPONSE`. It is `None` otherwise;
|
|
117
|
+
- `body`: the decoded response.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from sebpay import SebpayApiError
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
sebpay.checkout_sessions.create(...)
|
|
124
|
+
except SebpayApiError as e:
|
|
125
|
+
if e.code == "EXTERNAL_REFERENCE_CONFLICT":
|
|
126
|
+
... # This order id was already used with other parameters.
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Examples
|
|
130
|
+
|
|
131
|
+
See [`examples/`](https://github.com/mjims/sebpay-sdk-py/tree/main/examples). Set `SEBPAY_PUBLIC_KEY` and `SEBPAY_SECRET_KEY`, then:
|
|
132
|
+
- `python examples/create_checkout.py` creates a session and prints its `checkout_url`;
|
|
133
|
+
- `python examples/webhook_server.py` starts a Flask server (`pip install flask`) that handles the webhook and the return page, on `PORT` (default 3002).
|
|
134
|
+
|
|
135
|
+
For local testing, also set `SEBPAY_BASE_URL`.
|
|
136
|
+
|
|
137
|
+
## Tests
|
|
138
|
+
|
|
139
|
+
`python -m unittest discover -s tests` runs the SDK's offline tests. They cover the transport, against a local HTTP server, and the shared signature vectors.
|
|
140
|
+
|
|
141
|
+
## Requirements
|
|
142
|
+
|
|
143
|
+
Python 3.9+ and `requests`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
sebpay/__init__.py,sha256=UuigNpLJwqXRMhdBn0RrNi-EOvntvdIz-uqsvroIMwI,245
|
|
2
|
+
sebpay/_version.py,sha256=5ZigdfGhEeFjFeCFr4RORO5cwspqJiL1TNKf_jUYSmI,112
|
|
3
|
+
sebpay/client.py,sha256=_0AAJB75Q-Gk74ZKh32jxMPxAx8ht1Ma4iqFQFLXoVY,3144
|
|
4
|
+
sebpay/errors.py,sha256=Im5_LSIqXLoe81Sxli4FKV5E8oyESxIP_JyHSGPI9ZA,631
|
|
5
|
+
sebpay/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
sebpay/types.py,sha256=SlDrabcg7LBHJfxn7J_sveyMl-UatebY_UWX3NTiAhY,1385
|
|
7
|
+
sebpay/webhooks.py,sha256=0--Jxl6SO-S60dwFx3Jf8cygbvbqN-iZq5VP65GO4kY,901
|
|
8
|
+
sebpay/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
sebpay/resources/checkout_sessions.py,sha256=r27Tk9dpbyK3v_XtTsHNKyUZy-TFY79fXiVq7s2Haw4,1355
|
|
10
|
+
sebpay-0.2.0.dist-info/licenses/LICENSE,sha256=G0ZKDwtA6iyZ4FSBTtv7aTtF5_gqfi0pNl2iMRufKTg,1063
|
|
11
|
+
sebpay-0.2.0.dist-info/METADATA,sha256=DrnyCzeuAHngOIZbXvPj98FtMoq3JUbDoCrENh4J4FY,5303
|
|
12
|
+
sebpay-0.2.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
13
|
+
sebpay-0.2.0.dist-info/top_level.txt,sha256=XP-Kn6okZAJvxZZYrd_TWeq8CCzelx3_8C_covvf4Zk,7
|
|
14
|
+
sebpay-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sebpay
|
|
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 @@
|
|
|
1
|
+
sebpay
|