Metadata-Version: 2.4
Name: pinelabs-python
Version: 0.1.1
Summary: Official Pine Labs Online (Plural) Payment Gateway Python SDK.
Project-URL: Homepage, https://github.com/plural-pinelabs/pinelabs-python
Project-URL: Repository, https://github.com/plural-pinelabs/pinelabs-python
Project-URL: Documentation, https://docs.pluralonline.com
Project-URL: Bug Tracker, https://github.com/plural-pinelabs/pinelabs-python/issues
Author: Pine Labs
License: MIT
Keywords: api,payments,pinelabs,plural,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
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: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.8
Requires-Dist: httpx>=0.21.2
Requires-Dist: pydantic-core<3.0.0,>=2.18.2
Requires-Dist: pydantic>=1.9.2
Requires-Dist: typing-extensions>=4.0.0
Description-Content-Type: text/markdown

# pinelabs-python

[![pypi](https://img.shields.io/pypi/v/pinelabs-python.svg)](https://pypi.org/project/pinelabs-python/)
[![python](https://img.shields.io/pypi/pyversions/pinelabs-python.svg)](https://pypi.org/project/pinelabs-python/)
[![license](https://img.shields.io/pypi/l/pinelabs-python.svg)](#license)

Official Python SDK for the **Pine Labs Online Payment Gateway** (Plural).

## Install

```bash
pip install pinelabs-python
# or
poetry add pinelabs-python
# or
uv add pinelabs-python
```

Requires **Python ≥ 3.8**.

## Quickstart

The Pine Labs API uses **OAuth2 client_credentials**. Exchange your `<client-id>` / `<client-secret>` for an access token, then pass it to `PinelabsApi`.

```python
from pinelabs import Amount, PinelabsApi

# 1. Get a token. The /token endpoint does not require authentication, so we
#    pass token="" on this bootstrap client.
auth = PinelabsApi(
    base_url="https://pluraluat.v2.pinepg.in",  # or "https://api.pluralpay.in" for prod
    token="",
)

token_response = auth.authentication.generate_token(
    grant_type="client_credentials",
    client_id="<client-id>",
    client_secret="<client-secret>",
)

# 2. Build an authenticated client
client = PinelabsApi(
    base_url="https://pluraluat.v2.pinepg.in",
    token=token_response.access_token,
)

# 3. Call any operation
order = client.orders.create_order(
    merchant_order_reference="order-001",
    order_amount=Amount(value=50000, currency="INR"),  # ₹500.00
    # ...see API reference for the full schema
)

print(order)
```

For long-running processes, refresh the token before it expires using a token-supplier callable (see [Auto-refreshing token](#auto-refreshing-token) below).

## Environments

| Environment | Base URL                            |
| ----------- | ----------------------------------- |
| UAT         | `https://pluraluat.v2.pinepg.in`    |
| Production  | `https://api.pluralpay.in`          |

Pass the URL via `base_url`. The `PinelabsApiEnvironment` enum currently exposes only `PRODUCTION`; for UAT, set `base_url` explicitly.

```python
from pinelabs import PinelabsApi
from pinelabs.environment import PinelabsApiEnvironment

client = PinelabsApi(
    token="<access-token>",
    environment=PinelabsApiEnvironment.PRODUCTION,
)
```

## Auto-refreshing token

Pass a `token` callable instead of a string. The SDK invokes it on every request. Because the bootstrap `auth` client below does **not** use the supplier, there is no re-entrancy concern.

```python
import time
import threading
from typing import Optional
from pinelabs import PinelabsApi

base_url = "https://pluraluat.v2.pinepg.in"

# Bootstrap client used only to fetch tokens. It has no supplier, so calling
# generate_token() from inside get_token() will not recurse.
auth = PinelabsApi(base_url=base_url, token="")

_cached: Optional[dict] = None
_lock = threading.Lock()


def get_token() -> str:
    global _cached
    with _lock:
        if _cached and time.time() < _cached["expires_at"] - 30:
            return _cached["value"]
        r = auth.authentication.generate_token(
            grant_type="client_credentials",
            client_id="<client-id>",
            client_secret="<client-secret>",
        )
        _cached = {
            "value": r.access_token,
            "expires_at": time.time() + r.expires_in,
        }
        return _cached["value"]


client = PinelabsApi(base_url=base_url, token=get_token)
```

## Async client

The SDK also exports an `async` client for non-blocking calls. If you pass a custom `httpx_client`, use `httpx.AsyncClient()` instead of `httpx.Client()`.

```python
import asyncio
from pinelabs import Amount, AsyncPinelabsApi

client = AsyncPinelabsApi(token="<access-token>")


async def main() -> None:
    order = await client.orders.create_order(
        merchant_order_reference="order-001",
        order_amount=Amount(value=50000, currency="INR"),
    )
    print(order)


asyncio.run(main())
```

## Sub-clients

`PinelabsApi` exposes one sub-client per API tag:

| Sub-client                              | Purpose                                            |
| --------------------------------------- | -------------------------------------------------- |
| `client.authentication`                 | OAuth token generation                             |
| `client.orders`                         | Create / capture / cancel / fetch orders           |
| `client.refunds`                        | Create and look up refunds                         |
| `client.settlements`                    | Settlement reports + UTR lookup                    |
| `client.checkout`                       | Hosted-checkout related operations                 |
| `client.payment_links`                  | Single + bulk payment links                        |
| `client.card_payments`                  | Direct card payment + OTP                          |
| `client.bnpl`                           | Buy-Now-Pay-Later flows                            |
| `client.convenience_fee`                | Convenience-fee config + computation               |
| `client.e_challans`                     | Government e-challan integration                   |
| `client.apple_pay`                      | Apple Pay session + decryption                     |
| `client.international_payments`         | Cross-border payments                              |
| `client.customers`                      | Customer profile management                        |
| `client.tokenization`                   | Card / network tokenization                        |
| `client.payouts`                        | Payouts: balance, create, cancel, list             |
| `client.subscriptions_plans`            | Recurring-billing plans                            |
| `client.subscriptions_subscriptions`    | Subscription lifecycle                             |
| `client.subscriptions_presentations`    | Subscription debit presentations                   |
| `client.pay_by_points`                  | Loyalty / points-based payments                    |
| `client.affordability_suite`            | EMI / offer eligibility                            |
| `client.split_settlements`              | Split settlements between sub-merchants            |

For the full operation list, see the [API reference](https://docs.pluralonline.com/api-reference).

## Recipes

### Create a payment link

```python
from pinelabs import Amount, PinelabsApi

client = PinelabsApi(token="<access-token>")

link = client.payment_links.create_payment_link(
    merchant_payment_link_reference="link-001",
    amount=Amount(value=50000, currency="INR"),
    description="Order #001",
)
print(link)
```

### Fetch an order

```python
order = client.orders.get_order_by_id(order_id="v1-241010055924-aa-AHbN0s")
print(order)
```

### Refund an order

```python
from pinelabs.refunds import CreateRefundRequestOrderAmount

refund = client.refunds.create_refund(
    order_id="v1-241010055924-aa-AHbN0s",
    merchant_order_reference="refund-001",
    order_amount=CreateRefundRequestOrderAmount(value=400, currency="INR"),
)
print(refund)
```

## Error handling

All HTTP errors derive from `pinelabs.core.api_error.ApiError`. The SDK also exposes typed subclasses per status code:

```python
from pinelabs import PinelabsApi
from pinelabs.core.api_error import ApiError
from pinelabs.errors import (
    BadRequestError,
    UnauthorizedError,
    ForbiddenError,
    NotFoundError,
    ConflictError,
    UnprocessableEntityError,
    InternalServerError,
    ServiceUnavailableError,
)

client = PinelabsApi(token="<access-token>")

try:
    client.orders.get_order_by_id(order_id="missing")
except NotFoundError as e:
    print("order does not exist:", e.body)
except UnauthorizedError:
    print("token expired or invalid")
except ApiError as e:
    print("HTTP", e.status_code, e.body)
```

## Per-request options

Every operation accepts a `request_options` keyword argument:

```python
client.orders.create_order(
    merchant_order_reference="order-001",
    order_amount=Amount(value=50000, currency="INR"),
    request_options={
        "timeout_in_seconds": 30,
        "max_retries": 2,
        "additional_headers": {"x-correlation-id": "abc"},
    },
)
```

### Retries

The SDK retries with exponential backoff (default: **2** retries) on:

- `408` (Request Timeout)
- `429` (Too Many Requests)
- `5xx` (Server errors)

### Timeouts

The default timeout is **60 seconds**. Override at the client or per-request level:

```python
client = PinelabsApi(token="<access-token>", timeout=20.0)
```

### Access raw response data

```python
response = client.authentication.with_raw_response.generate_token(
    grant_type="client_credentials",
    client_id="<client-id>",
    client_secret="<client-secret>",
)
print(response.headers)
print(response.data)
```

### Custom HTTP client

```python
import httpx
from pinelabs import PinelabsApi

client = PinelabsApi(
    token="<access-token>",
    httpx_client=httpx.Client(
        proxy="http://my.test.proxy.example.com",
        transport=httpx.HTTPTransport(local_address="0.0.0.0"),
    ),
)
```

## Type hints

Full type hints (PEP 561) ship with the package. Request/response models live under `pinelabs.types`; resource-specific request models live under each sub-package (e.g. `pinelabs.refunds.CreateRefundRequestOrderAmount`).

## License

MIT
