Metadata-Version: 2.4
Name: forjio-serront
Version: 0.1.0
Summary: Serront SDK — typed Python client for the serront.com REST API. Sister to the JS + Go SDKs.
Project-URL: Homepage, https://serront.com/docs/sdk/python
Project-URL: Repository, https://github.com/hachimi-cat/saas-serront
Author-email: Forjio <support@forjio.com>
License: Proprietary
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# forjio-serront

Typed Python client for the [serront.com](https://serront.com) services-storefront REST API.

```bash
pip install forjio-serront
```

```python
from forjio_serront import SerrontClient

# Bearer token from token= or the SERRONT_TOKEN env var — use an
# sk_live_... API key from Dashboard -> Settings -> API keys.
client = SerrontClient(token="sk_live_xxx")

# Storefront — the seller's public profile
settings = client.storefront.get()  # None until the first put
client.storefront.put(
    slug="studio-renata",
    display_name="Studio Renata",
    bio="Brand & logo design studio.",
    whatsapp_number="+628120000000",
    manual_bank_accounts=[
        {"bankName": "BCA", "accountNumber": "1234567890", "accountHolder": "Renata"}
    ],
    published=True,
)

# Services — the catalog (fixed / hourly / package pricing)
services = client.services.list()
svc = client.services.create(
    slug="logo-design",
    name="Logo design",
    pricing_type="package",
    packages=[
        {"name": "Basic", "priceIdr": 500_000},
        {"name": "Full identity", "priceIdr": 2_500_000},
    ],
)
client.services.update(svc["id"], sort_order=1)

# Orders — the order desk
page = client.orders.list(status="requested", payment_status="payment_claimed", q="wedding")
order = client.orders.get(page["orders"][0]["id"])  # + full thread
client.orders.reply(order["id"], body="On it!", is_internal=False)
client.orders.update(order["id"], status="confirmed", quoted_price_idr=750_000)
client.orders.confirm_payment(order["id"])
proof = client.orders.download_proof(order["id"])  # {"data": bytes, "contentType"}

# Modules — Payment (Plugipay) / Marketing (Ripllo) integrations
modules = client.modules.get()
client.modules.set(payment=True)

# Ledger + payouts (Plugipay-held; Payment module required)
balance = client.ledger.balance()
entries = client.ledger.entries(limit=20)
payouts = client.payouts.list(status="pending")
payout = client.payouts.create(amount=1_000_000)
client.payouts.cancel(payout["id"])

# API keys + webhook subscriptions
keys = client.api_keys.list()
created = client.api_keys.create(name="ci")  # created["key"] shown ONCE
hook = client.webhook_subscriptions.create(
    url="https://example.com/serront-hook",
    events=["serront.order.created.v1"],
)  # hook["secret"] shown ONCE

# Billing — current plan + upgrade checkout (tiers: free / starter / growth / business)
info = client.billing.get()
out = client.billing.checkout("starter")  # redirect the browser to out["hostedUrl"]

# Public (buyer) surface — no token required
view = client.public.get_storefront("studio-renata")
placed = client.public.create_order(
    "studio-renata",
    service_slug="logo-design",
    package_name="Basic",
    buyer_name="Budi",
    buyer_email="budi@example.com",
    discount_code="WELCOME10",  # Marketing module
)
check = client.public.validate_discount("studio-renata", code="WELCOME10", price_idr=500_000)
my_order = client.public.get_order(placed["accessToken"])
client.public.reply_order(placed["accessToken"], body="Any update?")
with open("transfer.png", "rb") as f:
    client.public.upload_proof(placed["accessToken"], data=f.read(), content_type="image/png")
client.public.claim_payment(placed["accessToken"])  # "I have transferred"
pay = client.public.pay(placed["accessToken"])  # Payment module -> hostedUrl
```

## Endpoints covered

| Namespace | Methods |
| --- | --- |
| `storefront` | `get`, `put` (slug / display / bio / WhatsApp / bank accounts / branding / publish) |
| `services` | `list`, `create` (403 `LIMIT_REACHED` past the tier's service limit), `get`, `update`, `delete` (409 while orders reference it) |
| `orders` | `list` (status / payment_status / service_id / q / limit / cursor + per-status counts), `get`, `update` (status and/or quoted_price_idr), `reply` (with `is_internal`), `confirm_payment`, `download_proof` |
| `modules` | `get`, `set` (payment / marketing toggles; first enable provisions the partner workspace) |
| `ledger` | `balance`, `entries` (cursor-paged; 409 `PAYMENT_MODULE_DISABLED` when off) |
| `payouts` | `list`, `create`, `cancel`, `balance`, `get_bank_account`, `update_bank_account`, `mark_in_transit`, `mark_paid`, `mark_failed` |
| `api_keys` | `list`, `create` (plaintext returned once), `delete` |
| `webhook_subscriptions` | `list`, `create` (secret returned once), `update` (active), `delete` |
| `billing` | `get` (subscription + effectiveTier + tier table), `checkout(tier)` -> hosted checkout URL |
| `public` | `get_storefront`, `create_order`, `validate_discount`, `get_order`, `reply_order`, `claim_payment`, `pay`, `upload_proof` (no token) |

Errors raise `SerrontError` carrying the API envelope's `error.code`
(`NOT_FOUND`, `VALIDATION_ERROR`, `LIMIT_REACHED`, `UPGRADE_REQUIRED`,
`INVALID_TRANSITION`, ...), the HTTP status, and the `meta.requestId`.

## Family

Sister to:
- [`@forjio/serront`](https://www.npmjs.com/package/@forjio/serront) (JS/TS)
- [`hachimi-cat/serront-go`](https://github.com/hachimi-cat/serront-go) (Go)
