Metadata-Version: 2.4
Name: csobpg
Version: 0.6.1
Summary: Library for communication with ČSOB API
Author: Andrii Nechaiev
Author-email: Andrii Nechaiev <andrewnech@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development :: Libraries
Classifier: Operating System :: POSIX :: Linux
Classifier: Topic :: Utilities
Requires-Dist: pycryptodome>=3.17,<4
Requires-Dist: httprest>=0.3,<0.4
Requires-Python: >=3.9
Project-URL: Repository, https://github.com/litteratum/csobpg
Description-Content-Type: text/markdown

[![Codecov](https://codecov.io/gh/litteratum/csobpg/branch/master/graph/badge.svg)](https://codecov.io/gh/litteratum/csobpg)
# CSOB client
Python library for communicating with ČSOB (<https://platbakartou.csob.cz/>) payment gateway API. The API is described here: <https://github.com/csob/paymentgateway>.

The library currently implements ČSOB API v.1.9.


## Installation
```bash
pip install csobpg
```

## Python support
The library supports every Python version listed in the `classifiers` of [pyproject.toml](pyproject.toml).

Within that range, the versions shipped by Debian (currently 3.9, 3.11 and 3.13) get the most care: they are the ones the library is primarily developed and validated against. The library also always tries to support the latest released Python version.

The full range is tested locally with `nox -s tests`, while CI runs a representative subset of it.

## Basic usage
### API client initialization
The `APIClient` provides the interface to communicate with the API.

```python
from csobpg.v19 import APIClient

client = APIClient("merchantId", "merch_private.key", "csob.pub", base_url=..., http_client=...)

# Use the client to interact with the API:
client.init_payment(...)
```

### HTTP client
The library uses the [httprest](https://github.com/litteratum/httprest) library for making HTTP requests.
By default it will use `httprest.http.urllib_client.UrllibHTTPClient`.

But you may use any other `httprest's` HTTP client, or even write your own client.

## Base methods
The library supports all base API methods.
For example, that's how to initialize a payment:
```python
from csobpg.v19.models import cart

response = client.init_payment(
    order_no="2233823251",
    total_amount=100,
    return_url="http://127.0.0.1:5000",
    cart=cart.Cart([cart.CartItem("Apples", 1, 100)]),
    merchant_data=b"Hello, World!",
)
```

## OneClick methods
Here are the steps to perform a OneClick payment.

### Step 1 - make a regular payment
First, make a regular payment using the "payment/init":
```python
response = client.init_payment(
    ...,
    # this is important. It will tell the bank to create a OneClick template
    payment_operation=PaymentOperation.ONE_CLICK_PAYMENT,
)
# redirect to the payment page and finalize payment
client.get_payment_process_url(pid)
```

Preserve the `response.pay_id`, it will be used to refer to the OneClick template.

### Step 2 - initialize OneClick payment
Now, having the template ID, initialize the OneClick payment.
First, check that the template ID exists (optional, but recommended):
```python
response = client.oneclick_echo(template_id)
if not response.success:
    # OneClick template not found! handle it somehow
```

If the template exists, initiate the payment:
```python
response = client.oneclick_init_payment(
    pid, # this is the template ID (the ID of the initial payment, retrieved in Step 1)
    ...,
    client_ip="127.0.0.1", # this is mandatory when client_initiated=True
    client_initiated=True, # whether it is initiated in the presence of client or not
)
```

### Step 3 - process OneClick payment
Finally, process the payment:
```python
response = client.oneclick_process(
    pid, # this is the payment ID retrieved in Step 2
    Fingerprint( # mandatory only for client_initiated=True
        Browser( # the following values must be taken from the client's browser
            user_agent="requests",
            accept_header="application/json",
            language="eng",
            js_enabled=False,
        ),
    ),
)
```

## Google Pay methods
**WARNING**: not tested.

```python
# echo
response = client.googlepay_echo()

# payment Initialization
client.googlepay_init(
    pid, "127.0.0.1", 10000, {"Google Pay": "payload"}, "http://localhost"
)

# payment process
client.googlepay_process(pid, Fingerprint())
```

## Apple Pay methods
**WARNING**: not tested.

```python
# echo
response = client.applepay_echo()

# payment Initialization
client.applepay_init(
    pid, "127.0.0.1", 10000, {"Apple Pay": "payload"}, "http://localhost"
)

# payment process
client.applepay_process(pid, Fingerprint())
```

## Exceptions handling
```python
from csobpg.v19 import errors as _e
from httprest.http import HTTPRequestError

try:
    response = client.<operation>(...)
except _e.APIError as exc:
    # handle API error
    # it is raised on any API error. You may also catch the specific API error
except _e.APIInvalidSignatureError as exc:
    # handle invalid signature
except _e.APIInvalidResponseError as exc:
    # handle invalid API response
    # it is raised when the API returns something unexpected
    # (e.g. invalid JSON, missing signature, empty signature, malformed resultCode, etc.)

    # You can access the original HTTP response (httprest.http.HTTPResponse) for debugging
    exc.response
except _e.APIClientError as exc:
    # handle API client error. All unhandled exceptions fall into this category
except HTTPRequestError as exc:
    # handle HTTP error
    # it is raised if the HTTP request fails (e.g. connection error, timeout, etc.)
```

The library does not pre-validate the request parameters against the API specification. Whatever you pass is signed and sent as is, and the gateway is the one to reject it (reported as an `APIError`).

The library verifies the response signature before reporting the `resultCode` as an `APIError`. Mind that the API does not sign the requests it rejects before processing them:

```http
HTTP/1.1 401 Unauthorized

{"resultCode": 100, "resultMessage": "Missing parameter merchantId"}
```

Such a response cannot be verified, so an `APIError` alone is not a proof the failure was reported by the API. A successful response is always verified: it is never returned unless its signature matches.

## RSA keys management
The simples way to pass RSA keys is to pass their file paths:

```python
from csobpg.v19 import APIClient

client = APIClient(..., "merch_private.key", "csob.pub")
```

The library will read the private key from the file when needed. The public key will be cached into the RAM.

If you want to change it, use special classes:

```python
from csobpg.v19 import APIClient
from csobpg.v19.key import FileRSAKey, CachedRSAKey

client = APIClient(..., FileRSAKey("merch_private.key"), FileRSAKey("csob.pub"))
```

You may also override the base RSAKey class to define your own key access strategy:

```python
from csobpg.v19.key import RSAKey

class MyRSAKey(RSAKey):

    def __str__(self) -> str:
        return "my key"
```
