Metadata-Version: 2.4
Name: mongolian-payment-bonum
Version: 1.1.0
Summary: Bonum payment SDK for Python - Gateway Merchant API (invoices, tokenization, subscriptions, QR) plus PSP Apple Pay / Google Pay
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.8
Requires-Dist: httpx>=0.24.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Description-Content-Type: text/markdown

# mongolian-payment-bonum

Bonum payment SDK for Python (sync + async). Two clients in one package:

- **`BonumGatewayClient`** / **`AsyncBonumGatewayClient`** — the full Bonum
  Gateway Merchant API: Bearer-token auth (with auto-refresh), web-payment
  invoices, card tokenization, subscriptions, QR / deeplink, and webhook
  checksum verification.
- **`BonumClient`** / **`AsyncBonumClient`** — PSP card processing for Apple
  Pay, Google Pay and direct tokens (`psp.bonum.mn`).

## Installation

```bash
pip install mongolian-payment-bonum
```

Requires Python >= 3.8. Depends on `httpx`.

---

## Gateway client

```python
from mongolian_payment_bonum import (
    BonumGatewayClient, BonumGatewayConfig,
    CreateInvoiceInput, RequestCardTokenInput, PurchaseInput, CreateQrInput,
)

client = BonumGatewayClient(BonumGatewayConfig(
    base_url="https://testapi.bonum.mn",   # production: https://apis.bonum.mn
    app_secret="YOUR_APP_SECRET",
    terminal_id="YOUR_TERMINAL_ID",
    checksum_key="YOUR_CHECKSUM_KEY",      # for webhook verification
))

# 1) Web payment — create an invoice and redirect the payer
invoice = client.create_invoice(CreateInvoiceInput(
    amount=1000,
    callback="https://your-domain.com/webhook",
    transaction_id="ORDER-123",
    providers=["QPAY"],   # optional: QPAY | E_COMMERCE | WE_CHAT | SONO_SHOP
))
print(invoice.follow_up_link)   # redirect the payer here

# 2) Card tokenization + purchase
tok = client.request_card_token(RequestCardTokenInput(
    callback="https://your-domain.com/card-callback",
    transaction_id="TOK-1",
))
# after the CARD-TOKEN webhook delivers the token:
purchase = client.purchase_with_card_token(PurchaseInput(
    card_token="stored-card-token", amount=15000, transaction_id="PUR-1",
))
print(purchase.http_status)   # 200 = success, 201 = queued, 400 = declined

# 3) QR / deeplink
qr = client.create_qr(CreateQrInput(amount=10000, transaction_id="QR-1"))
# render qr.qr_image (base64) and qr.links (bank deeplinks)
```

Authentication is automatic: the client obtains a Bearer token via
`GET /bonum-gateway/ecommerce/auth/create` (`Authorization: AppSecret ...`,
`X-TERMINAL-ID`), caches it, and refreshes before expiry. Bonum rate-limits new
token requests, so reuse a client instance.

### Gateway methods

| Method | Description |
|--------|-------------|
| `get_payment_providers()` | List available invoice providers |
| `create_invoice(input)` | Create a web-payment invoice |
| `get_invoice(invoice_id)` | Get an invoice (test helper) |
| `request_card_token(input)` | Start card tokenization |
| `purchase_with_card_token(input)` | Charge a stored card token |
| `rollback_transaction(card_token, id)` | Refund a completed purchase |
| `list_payment_plans()` | List subscription plans |
| `subscribe(input)` | Subscribe a card token to a plan |
| `list_subscriptions(card_token)` | List a token's subscriptions |
| `change_subscription_token(id, card_token)` | Move a subscription to another token |
| `change_subscription_with_new_token(id)` | Link a brand-new token |
| `cancel_subscription(id)` | Cancel (active until next cycle) |
| `delete_subscription(id)` | Delete permanently |
| `create_qr(input)` | Create a QPay QR with bank deeplinks |
| `pay_qr(input)` | Pay a scanned QR with a stored card token |

`AsyncBonumGatewayClient` exposes the same methods as `async`.

### Webhook verification

Bonum signs deliveries with `HmacSHA256(raw_body, checksum_key)` (hex) in the
`x-checksum-v2` header. Verify over the **raw** request body:

```python
from mongolian_payment_bonum import verify_webhook_checksum, parse_webhook_event

ok = verify_webhook_checksum(raw_body, request.headers.get("x-checksum-v2"), checksum_key)
if ok:
    event = parse_webhook_event(raw_body)
    # event.type: PAYMENT | CARD-TOKEN | SUBSCRIPTION-PAYMENT
    # event.status: SUCCESS | FAILED
```

---

## PSP client (Apple Pay / Google Pay)

```python
from mongolian_payment_bonum import BonumClient, BonumConfig, ProcessPaymentInput

client = BonumClient(BonumConfig(
    endpoint="https://psp.bonum.mn",   # test: https://testpsp.bonum.mn
    merchant_key="your-merchant-key",
))
result = client.process_payment(ProcessPaymentInput(
    token="apple-pay-token", order_id="ORDER-001",
))
print(result.success, result.status_code)
logs = client.get_payment_log("ORDER-001")
```

`AsyncBonumClient` provides the same methods as `async` (use it as an
`async with` context manager).

| Method | Description |
|--------|-------------|
| `process_payment(input)` | Process payment with a card/Apple Pay token |
| `process_google_pay(input)` | Process a Google Pay payment |
| `validate_merchant(input)` | Validate an Apple Pay merchant session |
| `get_payment_log(order_id)` | Get payment log entries for an order |

---

## Configuration from environment variables

```python
from mongolian_payment_bonum import (
    BonumGatewayClient, load_gateway_config_from_env,
    BonumClient, load_config_from_env,
)

gateway = BonumGatewayClient(load_gateway_config_from_env())
psp = BonumClient(load_config_from_env())
```

```bash
# Gateway client
BONUM_GATEWAY_BASE_URL=https://testapi.bonum.mn
BONUM_APP_SECRET=your-app-secret
BONUM_TERMINAL_ID=your-terminal-id
BONUM_MERCHANT_CHECKSUM_KEY=your-checksum-key   # optional, for webhooks
BONUM_ACCEPT_LANGUAGE=mn                          # optional: mn | en

# PSP client
BONUM_ENDPOINT=https://psp.bonum.mn
BONUM_MERCHANT_KEY=your-merchant-key
```

> **Security.** Never hard-code or commit `APP_SECRET`, `MERCHANT_CHECKSUM_KEY`
> or merchant keys. Load them from the environment or a secrets vault. Apple Pay
> registration details are configured in the merchant portal — not in code.

## Endpoints

| Environment | Gateway | PSP |
|-------------|---------|-----|
| Production | `https://apis.bonum.mn` | `https://psp.bonum.mn` |
| Test | `https://testapi.bonum.mn` | `https://testpsp.bonum.mn` |

## Error handling

```python
from mongolian_payment_bonum import BonumError

try:
    gateway.create_invoice(input)
except BonumError as err:
    print(err, err.status_code, err.response)
```

## License

MIT
