Metadata-Version: 2.4
Name: plugipay
Version: 0.1.0
Summary: Official Python SDK for Plugipay — HMAC-signed REST client with full resource coverage (customers, plans, checkout sessions, invoices, subscriptions, refunds, adapters, payouts, ledger, reports, webhooks).
Project-URL: Homepage, https://plugipay.com
Project-URL: Documentation, https://plugipay.com/docs
Project-URL: Source, https://github.com/hachimi-cat/saas-plugipay/tree/master/sdk/python
Project-URL: Issues, https://github.com/hachimi-cat/saas-plugipay/issues
Author-email: Forjio <hello@plugipay.com>
License: MIT
Keywords: checkout,forjio,indonesia,midtrans,payments,plugipay,subscriptions,xendit
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Office/Business :: Financial
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21.0; extra == 'dev'
Description-Content-Type: text/markdown

# plugipay (Python)

Official Python SDK for [Plugipay](https://plugipay.com). HMAC-signed
REST client; works with Flask, FastAPI, Django, or anything else that
speaks Python 3.9+.

## Install

```bash
pip install plugipay
```

## Quickstart

```python
from plugipay import PlugipayClient

plug = PlugipayClient(key_id="ak_live_...", secret="...")

# Create a customer
customer = plug.customers.create(email="ada@example.com", name="Ada Lovelace")

# Open a checkout session
session = plug.checkout_sessions.create(
    amount=125000,
    currency="IDR",
    methods=["qris", "va", "ewallet"],
    success_url="https://yourapp.com/checkout/success",
    cancel_url="https://yourapp.com/checkout/cancel",
    customer_id=customer["id"],
)
print(session["hostedUrl"])

# Paginate customers
page = plug.customers.list(limit=20)
for c in page.data:
    print(c["id"], c.get("email"))
if page.has_more:
    next_page = plug.customers.list(limit=20, cursor=page.cursor)
```

The client is a context manager — `with PlugipayClient(...) as plug:`
will close the underlying httpx pool for you.

## Resource namespaces

Mirrors `@forjio/plugipay-node` 1:1:

| Namespace | Methods |
|---|---|
| `customers` | `create`, `get`, `list`, `update` |
| `plans` | `create`, `get`, `list`, `update`, `archive` |
| `checkout_sessions` | `create`, `get`, `list`, `cancel`, `confirm` |
| `invoices` | `create`, `get`, `list`, `finalize`, `pay`, `void`, `send_email` |
| `subscriptions` | `create`, `get`, `list`, `cancel`, `pause`, `resume` |
| `portal_sessions` | `create` |
| `receipts` | `list`, `get` |
| `payouts` | `create`, `get`, `list`, `cancel`, `mark_in_transit`, `mark_paid`, `mark_failed`, `balance`, `get_bank_account`, `update_bank_account` |
| `ledger` | `list`, `balances` |
| `reports` | `pnl`, `cash_flow` |
| `webhook_endpoints` | `list`, `create`, `delete` |
| `events` | `list`, `get` |
| `refunds` | `create`, `get`, `list` |
| `adapters` | `list`, `update_xendit`, `update_paypal`, `update_midtrans`, `update_manual`, `managed_onboarding_state`, `start_managed_onboarding`, `simulate_managed_onboarding` |
| `api_keys` | `list`, `create`, `revoke` |
| `billing` | `list_tiers`, `list_plans`, `refresh_tiers` |
| `onboarding` | `provision_managed` |
| `checkout_settings` | `get`, `update` |
| `templates` | `list`, `get`, `create`, `update`, `make_default`, `duplicate`, `preview`, `delete` |
| `uploads` | `image` |
| `workspaces` | `list`, `create`, `update`, `delete` |
| `account` | `get`, `update`, `list_sessions`, `revoke_session`, `revoke_all_sessions`, `list_linked`, `unlink`, `change_email`, `change_password`, `list_members` |
| `admin_portal` | `me`, `list_billing_accounts`, `update_billing_account`, `list_partners`, `create_partner`, `update_partner`, `delete_partner` |
| `admin` | `provision_workspace`, `get_workspace`, `partner_usage` |

## Webhooks

Plugipay signs every webhook with HMAC-SHA256 over `${timestamp}.${rawBody}`.
Pass the *raw* request body (bytes or str), not the parsed JSON.

```python
from flask import Flask, request, abort
from plugipay import verify_webhook, PlugipaySignatureError
import os

app = Flask(__name__)
SECRET = os.environ["PLUGIPAY_WEBHOOK_SECRET"]

@app.post("/webhooks/plugipay")
def plugipay_webhook():
    raw = request.get_data()
    sig = request.headers.get("X-Plugipay-Signature", "")
    try:
        event = verify_webhook(raw, sig, SECRET)
    except PlugipaySignatureError:
        abort(400)
    if event.type == "plugipay.invoice.paid.v1":
        invoice = event.object
        # ...
    return "", 204
```

## Platform keys

If you hold a platform-admin key acting across many merchants, scope
each call with `X-Plugipay-On-Behalf-Of` via `for_merchant()`:

```python
plat = PlugipayClient(key_id="ak_live_platform_...", secret="...")
for_acc = plat.for_merchant("acc_abc123")
for_acc.invoices.list()
```

## Errors

Every failure raises `PlugipayError` (or a subclass —
`PlugipayNetworkError`, `PlugipayTimeoutError`, `PlugipaySignatureError`).
The exception carries `.status`, `.code`, `.message`, and (when present)
`.request_id`.

```python
from plugipay import PlugipayClient, PlugipayError

plug = PlugipayClient(key_id="...", secret="...")
try:
    plug.customers.get("cus_does_not_exist")
except PlugipayError as e:
    print(e.status, e.code, e.message, e.request_id)
```

## Source

<https://github.com/hachimi-cat/saas-plugipay/tree/master/sdk/python>

## License

MIT
