Metadata-Version: 2.4
Name: bfinance-sdk
Version: 0.2.0
Summary: Official Python SDK for BFinance Business API
License: MIT
Keywords: bfinance,banking,cards,payments,fintech,api,sdk
Author: BFinance Technologies
Author-email: support@bfinance.app
Requires-Python: >=3.8
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
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: Topic :: Office/Business :: Financial
Provides-Extra: dev
Requires-Dist: black (>=23.0.0) ; extra == "dev"
Requires-Dist: mypy (>=1.5.0) ; extra == "dev"
Requires-Dist: pytest (>=7.4.0) ; extra == "dev"
Requires-Dist: pytest-cov (>=4.1.0) ; extra == "dev"
Requires-Dist: requests (>=2.31.0)
Requires-Dist: ruff (>=0.1.0) ; extra == "dev"
Requires-Dist: types-requests (>=2.31.0) ; extra == "dev"
Requires-Dist: typing-extensions (>=4.5.0) ; python_version < "3.10"
Project-URL: Bug Tracker, https://github.com/BFinance-Technologies/bfinance-sdk/issues
Project-URL: Documentation, https://docs.bfinance.app
Project-URL: Homepage, https://github.com/BFinance-Technologies/bfinance-sdk
Project-URL: Repository, https://github.com/BFinance-Technologies/bfinance-sdk
Description-Content-Type: text/markdown

# BFinance SDK

This SDK allows you to integrate BFinance Business services into your app.

## Installation

```bash
pip install bfinance-sdk
```

## Initialization

### Configuration object

```python
from bfinance import BFinance

client = BFinance(
    api_token="YOUR_BEARER_TOKEN",
    base_url="https://business.bfinance.app",  # optional
    timeout_ms=3000,  # optional
)
```

**Config fields:**

- `api_token` (required) — Bearer token
- `base_url` (optional) — API base URL, defaults to `https://business.bfinance.app`
- `timeout_ms` (optional) — request timeout in ms, defaults to 30000 (30 seconds)
- `logging` (optional) — request/response logging configuration

```python
from bfinance.config import HttpLoggingConfig

client = BFinance(
    api_token="YOUR_BEARER_TOKEN",
    logging=HttpLoggingConfig(
        level="info",  # "none" | "error" | "info" | "debug"
        include_body=False,
        include_headers=False,
    )
)
```

### Authentication

The SDK uses Bearer token authorization for all requests:

- Header: `Authorization: Bearer <api_token>`

Keep credentials secure and do not expose tokens in client-side apps.

## HTTP Layer: Errors, Interceptors, Base Service

The SDK uses a shared HTTP layer for all API calls.
It provides consistent error handling, supports interceptors for logging/retries, and encapsulates common request logic in a base service class.

---

### Error Handling

All SDK methods raise standardized errors. Errors are split into two major categories:

#### 1) HTTP errors (API responded with 4xx/5xx)

These errors occur when the API returns a non-success HTTP status code.

Typical cases:

- `400` — validation error
- `401/403` — invalid or missing authorization
- `404` — resource not found
- `409` — conflict (if applicable)
- `429` — rate limit exceeded
- `5xx` — server errors

**What you get:**

- `status_code` — HTTP status code
- `message` — readable error message
- `raw` — raw response body (if any)

#### 2) Network & client errors (no valid API response)

These errors occur when the request cannot be completed due to network or client-side problems:

- request timeout
- DNS / connectivity issues
- TLS handshake errors
- connection reset
- invalid / unparseable response body (rare)

---

#### 3) Recommended error handling pattern

**HttpError** (API responded with 4xx/5xx)

Raised when API returns non-2xx status code.

- `status_code` — HTTP status code
- `message` — readable error message
- `raw` — raw response body (if any)

**NetworkError** (no valid API response)

Raised for network/client issues:

- timeouts
- DNS/connectivity issues
- connection resets
- TLS errors

```python
from bfinance.http.errors import HttpError, NetworkError

try:
    customer = client.customers.get_by_id("123")
except HttpError as err:
    print(f"HTTP error: {err.status_code} {err.message}")
except NetworkError as err:
    print(f"Network error: {err.message}")
except Exception as err:
    print(f"Unknown error: {err}")
```

#### 4) Logging (interceptors)

```python
from bfinance import BFinance
from bfinance.config import HttpLoggingConfig

client = BFinance(
    api_token="YOUR_BEARER_TOKEN",
    logging=HttpLoggingConfig(
        level="info",  # "none" | "error" | "info" | "debug"
        include_body=False,
        include_headers=False,
    )
)
```

## Card Management

The SDK provides card management through three modules:

- `client.prepaid_cards` — prepaid cards lifecycle and operations
- `client.budget_cards` — budget-linked cards lifecycle and operations
- `client.physical_cards` — ordering and activating physical cards

> **Security:** Methods that return sensitive card data (PAN/CVC/expiry) must never be logged.

---

### Prepaid Cards (`client.prepaid_cards`)

#### Available methods

- `prepaid_cards.get_list(page=None, limit=None)` — get a paginated list of cards

**Parameters:**

- `page` (optional, int) — page number, default: 1
- `limit` (optional, int) — items per page, default: 10

**Response:**

```python
{
    "status": "success",
    "data": {
        "cards": [
            {
                "id": "card_123",
                "maskedCardNumber": "**** 1234",
                "currency": "USD",
                "status": "active",
                "externalCardId": "ext_123"
            }
        ],
        "page": 1,
        "limit": 10
    }
}
```

---

- `prepaid_cards.issue(type_id, first_name, last_name, initial_balance, label=None)` — issue a new card

**Parameters:**

- `type_id` (str) — card type ID (e.g., "standard", "premium")
- `first_name` (str) — cardholder first name
- `last_name` (str) — cardholder last name
- `initial_balance` (int) — initial balance in dollars (e.g., 100 = $100.00)
- `label` (optional, str) — card label/description

**Response:**

```python
{
    "status": "success",
    "data": {
        "card": {
            "id": "card_123",
            "cardNumber": "4111111111111111",
            "cardExpire": "12/25",
            "cardCVC": "123",
            "cardBalance": 100,
            "currency": "USD",
            "maskedCardNumber": "**** 1234",
            "brand": "visa",
            "label": "Employee card"
        }
    }
}
```

> **Warning:** Response contains sensitive card data (full number, CVV). Handle securely!

---

- `prepaid_cards.reissue(card_id, initial_balance)` — reissue an existing card

**Parameters:**

- `card_id` (str) — existing card ID
- `initial_balance` (int) — balance for new card in dollars

---

- `prepaid_cards.get_details(card_id)` — get detailed card information

**Parameters:**

- `card_id` (str) — card ID

---

- `prepaid_cards.get_transactions(card_id, page=None, limit=None)` — get card transaction history

**Parameters:**

- `card_id` (str) — card ID
- `page` (optional, int) — page number
- `limit` (optional, int) — transactions per page

---

- `prepaid_cards.get_sensitive(card_id)` — get sensitive card data (PAN, CVV, expiry)

**Parameters:**

- `card_id` (str) — card ID

> **Warning:** ⚠️ NEVER LOG THIS DATA! Contains full card number and CVV.

---

- `prepaid_cards.freeze(card_id)` — freeze a card (block transactions)

- `prepaid_cards.unfreeze(card_id)` — unfreeze a card

- `prepaid_cards.delete(card_id)` — permanently delete a card

---

- `prepaid_cards.update_email(card_id, email)` — update card email

- `prepaid_cards.update_phone_number(card_id, phone)` — update card phone

- `prepaid_cards.set_pin(card_id, pin)` — set card PIN code

---

- `prepaid_cards.top_up(card_id, amount, idempotency_key=None)` — add funds to card

**Parameters:**

- `card_id` (str) — card ID
- `amount` (int) — amount to add in dollars (e.g., 100 = $100.00)
- `idempotency_key` (optional, str) — unique key to prevent duplicate charges

**Example:**

```python
import uuid

# Generate unique key for this operation
key = str(uuid.uuid4())

try:
    response = client.prepaid_cards.top_up(
        "card_123",
        amount=50,  # $50.00
        idempotency_key=key
    )
except NetworkError:
    # Safe to retry with same key!
    response = client.prepaid_cards.top_up("card_123", 50, key)
```

---

- `prepaid_cards.withdraw_funds(card_id, amount, idempotency_key=None)` — withdraw funds from card

**Parameters:**

- `card_id` (str) — card ID
- `amount` (int) — amount to withdraw in dollars
- `idempotency_key` (optional, str) — unique key to prevent duplicate operations

---

- `prepaid_cards.approve_transaction(card_id, action_id)` — approve a pending transaction

- `prepaid_cards.generate_topup_address(card_id, currency, network)` — generate crypto deposit address

- `prepaid_cards.get_spending_limits(card_id)` — get current spending limits

- `prepaid_cards.set_spending_limits(card_id, **limits)` — set spending limits

**Example:**

```python
client.prepaid_cards.set_spending_limits(
    "card_123",
    daily_limit=500,       # $500 per day
    weekly_limit=2000,     # $2000 per week
    monthly_limit=5000,    # $5000 per month
    per_transaction_limit=100  # $100 per transaction
)
```

---

### Budget Cards (`client.budget_cards`)

Budget cards are linked to a monthly/weekly budget and automatically reset.

#### Available methods:

- `budget_cards.issue(**card_data)` — issue a budget card
- `budget_cards.get_by_id(card_id)` — get budget card details
- `budget_cards.freeze(card_id)` — freeze budget card
- `budget_cards.unfreeze(card_id)` — unfreeze budget card
- `budget_cards.delete(card_id)` — delete budget card
- `budget_cards.set_pin(card_id, pin)` — set PIN
- `budget_cards.update_email(card_id, email)` — update email
- `budget_cards.update_phone_number(card_id, phone)` — update phone
- `budget_cards.set_velocity_limits(card_id, **limits)` — set transaction velocity limits
- `budget_cards.get_sensitive(card_id)` — get sensitive card data

---

### Physical Cards (`client.physical_cards`)

#### Available methods:

- `physical_cards.order(card_id)` — order a physical card for an existing virtual card
- `physical_cards.activate(card_id)` — activate a received physical card

---

## Customer Management

### Customers (`client.customers`)

#### Available methods:

- `customers.create_individual(**customer_data)` — create a new individual customer account

**Example:**

```python
customer = client.customers.create_individual(
    type="individual",
    firstName="John",
    lastName="Doe",
    email="john@example.com",
    birthDate="1990-01-01",
    nationality="USA"  # ISO-3 country code
)
```

- `customers.get_by_id(customer_id)` — get customer by ID
- `customers.create_via_sumsub(sumsub_token)` — create customer via Sumsub KYC
- `customers.request_feature_access(customer_id, feature_type, **options)` — request access to features (cards, accounts, etc.)

---

## Virtual Accounts

### Virtual Accounts (`client.virtual_accounts`)

#### Available methods:

- `virtual_accounts.get_list(customer_id)` — list customer's virtual accounts
- `virtual_accounts.get_eligibility(customer_id)` — check if customer can create virtual account
- `virtual_accounts.create(customer_id, currency)` — create a virtual account

---

## Disputes

### Disputes (`client.disputes`)

#### Available methods:

- `disputes.create(**dispute_data)` — create a dispute for unauthorized/fraudulent transaction

**Example:**

```python
dispute = client.disputes.create(
    transaction_id="txn_123",
    reason="unauthorized",
    description="Card was stolen. Transaction not authorized by me.",
    amount=150,  # $150.00
    evidence_file_urls=["https://storage.example.com/police-report.pdf"]
)
```

- `disputes.get_status(dispute_id)` — get dispute status and timeline
- `disputes.cancel(dispute_id)` — cancel a dispute

---

## eSIM & Mobile Data

### eSIM (`client.esim`)

Purchase mobile data packages for international travel.

#### Available methods:

- `esim.get_countries()` — get list of available countries
- `esim.get_regions()` — get list of regions
- `esim.get_country_packages(country_code)` — get data packages for specific country
- `esim.get_global_packages()` — get global data packages
- `esim.get_regional_packages(region_id)` — get packages for a region
- `esim.get_package_details(package_id)` — get package details
- `esim.purchase_package(package_id, **options)` — purchase a data package
- `esim.get_details(esim_id)` — get eSIM details and QR code

---

## Utilities

### Utils (`client.utils`)

#### Available methods:

- `utils.validate_iban(iban)` — validate IBAN format and checksum

**Example:**

```python
result = client.utils.validate_iban("GB82WEST12345698765432")
if result["data"]["valid"]:
    print("IBAN is valid")
```

- `utils.get_bank_by_swift(swift_code)` — get bank information by SWIFT/BIC code

- `utils.get_mcc_description(mcc_code)` — get merchant category description

**Example:**

```python
# Get category for restaurant transaction
description = client.utils.get_mcc_description("5812")
print(description["data"]["description"])  # "Eating Places, Restaurants"
```

---

## Requirements

- Python 3.8+
- requests >= 2.31.0

## License

MIT

## Support

- Documentation: https://docs.bfinance.app
- GitHub Issues: https://github.com/BFinance-Technologies/python-sdk/issues
- Email: support@bfinance.app

