From dbaf624c945bfa38af6124e27b50bbfba1b2c3c3 Mon Sep 17 00:00:00 2001 From: Einswilli Date: Fri, 26 Sep 2025 00:22:31 +0200 Subject: [PATCH] Docs: Add API-reference Docs --- docs/api-reference.md | 1 - docs/api-reference/base-adapter.md | 251 +++++++++++++++++ docs/api-reference/config-types.md | 187 ++++++++++++ docs/api-reference/exceptions.md | 173 ++++++++++++ docs/api-reference/shared-types.md | 266 ++++++++++++++++++ docs/assets/javascript/init_kapa_widget.v2.js | 56 ++-- docs/contributing.md | 8 +- easyswitch/adapters/base.py | 7 +- easyswitch/integrators/cinetpay.py | 5 +- mkdocs.yml | 8 +- pyproject.toml | 6 + 11 files changed, 928 insertions(+), 40 deletions(-) delete mode 100644 docs/api-reference.md create mode 100644 docs/api-reference/base-adapter.md create mode 100644 docs/api-reference/config-types.md create mode 100644 docs/api-reference/exceptions.md create mode 100644 docs/api-reference/shared-types.md diff --git a/docs/api-reference.md b/docs/api-reference.md deleted file mode 100644 index 2e03555..0000000 --- a/docs/api-reference.md +++ /dev/null @@ -1 +0,0 @@ -# Under development \ No newline at end of file diff --git a/docs/api-reference/base-adapter.md b/docs/api-reference/base-adapter.md new file mode 100644 index 0000000..a1e52c5 --- /dev/null +++ b/docs/api-reference/base-adapter.md @@ -0,0 +1,251 @@ +# Base Adapter (`easyswitch.adapters.base`) + +This module provides the **foundation of EasySwitch’s adapter system**. + +* An **adapter** is a small class that knows how to talk to a specific **payment provider (aggregator)**. +* Each adapter implements the same **common interface**, so that EasySwitch can interact with **any provider** in a consistent way. +* The **adapter registry** keeps track of all registered providers, so you can dynamically load them at runtime. + +--- + +## πŸ”Ή `AdaptersRegistry` + +The registry is the **central directory of all adapters**. +Instead of hardcoding adapter classes, EasySwitch lets you **register** and **retrieve** them dynamically. + +Think of it as a plugin manager: + +* Developers implement an adapter for a new provider. +* They register it under a **provider name**. +* Later, EasySwitch can fetch it by name and use it. + +### Methods + +#### `AdaptersRegistry.register(name: Optional[str] = None)` + +Decorator used to register an adapter under the given name. + +* If `name` is provided β†’ adapter is registered under that name. +* If omitted β†’ EasySwitch will use the adapter class name (`SemoaAdapter β†’ semoa`). + +```python +@AdaptersRegistry.register("semoa") +class SemoaAdapter(BaseAdapter): + ... +``` + +This means you can later do: + +```python +adapter_cls = AdaptersRegistry.get("semoa") +adapter = adapter_cls(config=my_provider_config) +``` + +--- + +#### `AdaptersRegistry.get(name: str) -> Type[BaseAdapter]` + +Fetches an adapter by name. + +* If found β†’ returns the adapter **class** (not an instance). +* If not found β†’ raises `InvalidProviderError`. + +--- + +#### `AdaptersRegistry.all() -> List[Type[BaseAdapter]]` + +Returns a list of **all registered adapter classes**. +Useful for debugging or auto-loading providers. + +--- + +#### `AdaptersRegistry.list() -> List[str]` + +Returns just the **names** of all registered adapters. + +```python +print(AdaptersRegistry.list()) +# ["semoa", "wave", "mtn", ...] +``` + +--- + +#### `AdaptersRegistry.clear() -> None` + +Removes all registered adapters (used in tests). + +--- + +## πŸ”Ή `BaseAdapter` + +The **abstract base class** for all adapters. +Every provider must implement this interface to ensure consistency across EasySwitch. + +It defines: + +* βœ… Common configuration logic (sandbox/production, client setup). +* βœ… Utility methods (validation, required fields, formatting). +* βœ… Abstract methods that **MUST** be implemented per provider. + +--- + +### Class Attributes + +* `REQUIRED_FIELDS: List[str]` β†’ List of required fields (ex: `["api_key", "merchant_id"]`). +* `SANDBOX_URL: str` β†’ Provider sandbox base URL. +* `PRODUCTION_URL: str` β†’ Provider production base URL. +* `SUPPORTED_CURRENCIES: List[Currency]` β†’ Currencies supported by the provider. +* `MIN_AMOUNT: Dict[Currency, float]` β†’ Minimum transaction amount per currency. +* `MAX_AMOUNT: Dict[Currency, float]` β†’ Maximum transaction amount per currency. +* `VERSION: str` β†’ Version of the adapter (default `"1.0.0"`). +* `client: Optional[HTTPClient]` β†’ Reusable HTTP client instance. + +--- + +### Constructor + +```python +def __init__(self, config: ProviderConfig, context: Optional[Dict[str, Any]] = None) +``` + +* `config` β†’ Holds provider credentials and environment info (sandbox/production). +* `context` β†’ Optional dict with extra metadata (e.g., debug flags, request ID, etc.). + +This constructor is automatically called when you **instantiate** an adapter. + +--- + +### Utility Methods + +#### `get_client() -> HTTPClient` + +* Ensures an HTTP client is available. +* Reuses the same client for performance. + +#### `get_context() -> Dict[str, Any]` + +* Returns extra context passed at instantiation. +* Useful for logging, tracing, or debugging. + +#### `supports_partial_refund() -> bool` + +* Returns `True` if the provider supports **partial refunds**. +* Default: `False`. + +#### `provider_name() -> str` + +* Returns a **normalized provider name**. +* E.g. `SemoaAdapter` β†’ `"semoa"`. + +--- + +### Abstract Methods (Must Be Implemented) + +Every adapter **must implement** the following methods. + +| Method | Purpose | Example Use | +| ---------------------------------------- | ------------------------------------------------ | -------------------------------------- | +| `get_headers(authorization=False)` | Build HTTP headers for requests | Add `"Authorization: Bearer "` | +| `get_credentials()` | Return provider credentials | Used internally to sign requests | +| `send_payment(transaction)` | Send a new payment request | User pays via Semoa/MTN/Wave | +| `check_status(transaction_id)` | Query transaction status | Polling until success/failure | +| `cancel_transaction(transaction_id)` | Cancel a pending transaction | Not all providers support it | +| `get_transaction_detail(transaction_id)` | Get detailed transaction info | Fetch amount, payer, status | +| `refund(transaction_id, amount, reason)` | Process a refund | Full or partial refund | +| `validate_webhook(payload, headers)` | Verify incoming webhook signature | Prevent spoofed requests | +| `parse_webhook(payload, headers)` | Parse provider webhook β†’ EasySwitch format | Normalize webhook events | +| `validate_credentials(credentials)` | Ensure credentials are valid | Check API key correctness | +| `format_transaction(data)` | Convert EasySwitch transaction β†’ provider format | For sending requests | +| `get_normalize_status(status)` | Map provider status β†’ standardized status | `"paid"` β†’ `TransactionStatus.SUCCESS` | + +--- + +### Validation Methods + +#### `get_required_fields() -> List[str]` + +Returns the required config fields for this adapter. + +#### `validate_transaction(transaction: TransactionDetail) -> bool` + +Checks if the transaction is valid: + +* Amount within min/max range. +* Currency supported. +* Phone number format valid. + +Raises exception if invalid. + +--- + +### URL Resolver + +#### `_get_base_url() -> str` + +Returns the correct base URL depending on the environment: + +* Sandbox β†’ `SANDBOX_URL`. +* Production β†’ `PRODUCTION_URL`. + +--- + +## βœ… Example – Implementing a Custom Adapter + +```python +from easyswitch.adapters.base import BaseAdapter, AdaptersRegistry +from easyswitch.types import PaymentResponse, TransactionDetail, TransactionStatus + +@AdaptersRegistry.register("semoa") +class SemoaAdapter(BaseAdapter): + SANDBOX_URL = "https://sandbox.semoa.com/api" + PRODUCTION_URL = "https://api.semoa.com" + SUPPORTED_CURRENCIES = ["XOF"] + + def get_headers(self, authorization=False): + return { + "Authorization": f"Bearer {self.config.api_key}" if authorization else "", + "Content-Type": "application/json" + } + + def get_credentials(self): + return self.config + + async def send_payment(self, transaction: TransactionDetail) -> PaymentResponse: + # TODO: Call Semoa API + ... + + async def check_status(self, transaction_id: str) -> TransactionStatus: + # TODO: Implement status polling + ... + + async def cancel_transaction(self, transaction_id: str) -> bool: + return False # not supported + + async def refund(self, transaction_id: str, amount=None, reason=None) -> PaymentResponse: + ... + + async def validate_webhook(self, payload, headers) -> bool: + return True + + async def parse_webhook(self, payload, headers) -> dict: + return {"status": "parsed"} + + def validate_credentials(self, credentials) -> bool: + return bool(credentials.api_key) +``` + +--- + +## πŸ“ Developer Checklist for Writing a New Adapter + +Before publishing your adapter, make sure you: + +* [ ] Define `SANDBOX_URL` and `PRODUCTION_URL`. +* [ ] Set `SUPPORTED_CURRENCIES`. +* [ ] Implement `send_payment()`. +* [ ] Implement `check_status()`. +* [ ] Implement `refund()` (if supported). +* [ ] Handle webhooks: `validate_webhook()` + `parse_webhook()`. +* [ ] Normalize provider-specific statuses with `get_normalize_status()`. +* [ ] Validate credentials in `validate_credentials()`. +* [ ] Add proper headers in `get_headers()`. diff --git a/docs/api-reference/config-types.md b/docs/api-reference/config-types.md new file mode 100644 index 0000000..6952900 --- /dev/null +++ b/docs/api-reference/config-types.md @@ -0,0 +1,187 @@ +# Configuration Models (`easyswitch.conf.base`) + +This module defines the **configuration system** for EasySwitch. +It provides base classes, validation logic, and standardized structures to configure providers, logging, and root settings. + +--- + +## πŸ”Ή Enumerations + +### `LogLevel` + +Defines the available **logging levels**. + +| Value | Description | +| ---------- | --------------------------------- | +| `debug` | Detailed debugging logs. | +| `info` | General information logs. | +| `warning` | Warnings that may need attention. | +| `error` | Errors that occurred. | +| `critical` | Critical errors, system failures. | + +--- + +### `LogFormat` + +Defines the available **logging output formats**. + +| Value | Description | +| ------- | ------------------------------- | +| `plain` | Standard human-readable logs. | +| `json` | Structured logs in JSON format. | + +--- + +## πŸ”Ή Models + +### `LoggingConfig` + +Configuration model for **application logging**. + +```python +class LoggingConfig(BaseModel): + enabled: bool = False + level: LogLevel = LogLevel.INFO + file: Optional[str] = None + console: bool = True + max_size: int = 10 # MB + backups: int = 5 + compress: bool = True + format: LogFormat = LogFormat.PLAIN + rotate: bool = True +``` + +**Fields:** + +* `enabled` – Enable/disable logging (`False` by default). +* `level` – Log level (`LogLevel` enum). +* `file` – File path for logs (if any). +* `console` – Print logs to console. +* `max_size` – Maximum file size before rotation (MB). +* `backups` – Number of backup log files to keep. +* `compress` – Whether to compress rotated logs. +* `format` – Log format (`plain` or `json`). +* `rotate` – Enable log rotation. + +--- + +### `BaseConfigModel` + +A **base class** for all configuration models. +Provides extra validation rules via Pydantic. + +* Forbids extra/undefined fields. +* Enforces enum values. +* Validates all fields strictly. + +--- + +### `ProviderConfig` + +Defines configuration for a **payment provider**. + +```python +class ProviderConfig(BaseConfigModel): + api_key: Optional[str] = None + api_secret: Optional[str] = None + token: Optional[str] = None + base_url: Optional[str] = None + callback_url: Optional[str] = None + return_url: Optional[str] = None + timeout: int = 30 + environment: str = "sandbox" # "sandbox" | "production" + extra: Dict[str, Any] = {} +``` + +**Validations:** + +* `environment` must be `"sandbox"` or `"production"`. +* At least one of `api_key` or `api_secret` must be provided. + +**Fields:** + +* `api_key`, `api_secret`, `token` – Authentication credentials. +* `base_url` – Provider API base URL. +* `callback_url` – Callback URL for webhooks. +* `return_url` – URL to redirect users after a transaction. +* `timeout` – API request timeout (seconds). +* `environment` – `"sandbox"` or `"production"`. +* `extra` – Extra provider-specific settings. + +--- + +### `RootConfig` + +The **root configuration** for EasySwitch. + +```python +class RootConfig(BaseConfigModel): + debug: bool = False + logging: LoggingConfig = Field(default_factory=LoggingConfig) + default_currency: str = Currency.XOF + providers: Dict[Provider, ProviderConfig] = Field(default_factory=dict) + default_provider: Optional[Provider] = None +``` + +**Fields:** + +* `debug` – Enable debug mode if `True`. +* `logging` – Logging configuration (`LoggingConfig`). +* `default_currency` – Default currency (`Currency` enum). +* `providers` – Dictionary of enabled providers (`ProviderConfig` per provider). +* `default_provider` – Default provider (must exist in `providers`). + +**Validations:** + +* `default_provider` must be: + + * Included in the enabled `providers`. + * A valid supported provider (`Provider` enum). +* `default_currency` must be a valid value in `Currency`. + +--- + +### `BaseConfigSource` + +An abstract base class (interface) for **configuration sources**. +Any custom configuration loader (e.g., from environment, file, database) must implement it. + +```python +class BaseConfigSource(ABC): + @abstractmethod + def load(self) -> Dict[str, Any]: + """Load configurations from the source.""" + pass + + @abstractmethod + def is_valid(self) -> bool: + """Check if the source is valid.""" + pass +``` + +--- + +## βœ… Example Usage + +```python +from easyswitch.conf.base import RootConfig, ProviderConfig, LoggingConfig, LogLevel, LogFormat +from easyswitch.types import Provider, Currency + +config = RootConfig( + debug=True, + logging=LoggingConfig( + enabled=True, + level=LogLevel.DEBUG, + format=LogFormat.JSON + ), + default_currency=Currency.XOF, + providers={ + Provider.SEMOA: ProviderConfig( + api_key="your-api-key", + api_secret="your-api-secret", + environment="sandbox" + ) + }, + default_provider=Provider.SEMOA +) +``` diff --git a/docs/api-reference/exceptions.md b/docs/api-reference/exceptions.md new file mode 100644 index 0000000..cbfae9c --- /dev/null +++ b/docs/api-reference/exceptions.md @@ -0,0 +1,173 @@ +# ⚠️ Exception Handling in EasySwitch + +EasySwitch defines a **hierarchy of custom exceptions** to provide clear and structured error handling. +Instead of catching generic Python exceptions, you can catch **specific errors** raised by the SDK or providers. + +This makes it easier to: + +* Handle provider-specific failures. +* Distinguish between **configuration issues**, **API failures**, and **validation errors**. +* Implement robust retry and fallback strategies. + +--- + +## πŸ”Ή Exception Hierarchy + +``` +Exception +└── EasySwitchError + β”œβ”€β”€ ConfigurationError + β”œβ”€β”€ AuthenticationError + β”œβ”€β”€ InvalidRequestError + β”œβ”€β”€ ValidationError + β”œβ”€β”€ NetworkError + β”œβ”€β”€ InvalidProviderError + β”œβ”€β”€ TransactionNotFoundError + β”œβ”€β”€ WebhookValidationError + β”œβ”€β”€ UnsupportedOperationError + β”œβ”€β”€ APIError + β”‚ β”œβ”€β”€ RateLimitError + β”‚ β”œβ”€β”€ PaymentError + β”‚ β”œβ”€β”€ WebhookError + β”‚ β”œβ”€β”€ CustomerError + β”‚ β”œβ”€β”€ CurrencyError + β”‚ β”œβ”€β”€ RefundError + β”‚ β”œβ”€β”€ CancellationError + β”‚ β”œβ”€β”€ BalanceError + β”‚ └── LogError +``` + +--- + +## πŸ”Ή `EasySwitchError` + +The **base exception** for the SDK. +All other errors inherit from this class. + +```python +class EasySwitchError(Exception): + def __init__(self, message: str, code: Optional[str] = None, details: Optional[Dict[str, Any]] = None): + self.message = message + self.code = code + self.details = details or {} +``` + +βœ… Attributes: + +* `message` β†’ Human-readable description of the error. +* `code` β†’ Short identifier for the error (optional). +* `details` β†’ Dict with structured information about the error. + +--- + +## πŸ”Ή Configuration & Authentication Errors + +* **`ConfigurationError`** β†’ Misconfigured SDK or provider settings. + + * Example: Missing API key or wrong environment value. + +* **`AuthenticationError`** β†’ Failed authentication with the provider. + + * Example: Invalid API key/secret. + +* **`InvalidRequestError`** β†’ Request built incorrectly before being sent. + + * Example: Missing required parameters. + +* **`ValidationError`** β†’ A specific request field is invalid. + + * Extra field: `field` β†’ name of the invalid field. + +```python +try: + client = EasySwitchClient(config={}) +except ConfigurationError as e: + print(f"Invalid configuration: {e.message}") +``` + +--- + +## πŸ”Ή API Errors + +`APIError` is the **base class** for provider-related failures. +It contains extra metadata to help debugging: + +βœ… Attributes: + +* `status_code` β†’ HTTP status code (if available). +* `provider` β†’ Provider name (e.g. `"mtn"`, `"wave"`). +* `raw_response` β†’ Full API response from provider. + +Subclasses of `APIError`: + +* **`RateLimitError`** β†’ Too many requests sent in a short time. +* **`PaymentError`** β†’ Error during payment processing. +* **`RefundError`** β†’ Refund request failed. +* **`CancellationError`** β†’ Transaction cancellation failed. +* **`WebhookError`** β†’ Error while processing a webhook. +* **`CustomerError`** β†’ Customer creation/management failure. +* **`CurrencyError`** β†’ Unsupported or invalid currency. +* **`BalanceError`** β†’ Balance retrieval failed. +* **`LogError`** β†’ Error related to logging or audit logs. + +```python +try: + payment = client.send_payment(transaction) +except PaymentError as e: + print(f"Payment failed: {e.details.get('raw_response')}") +``` + +--- + +## πŸ”Ή Network & Provider Errors + +* **`NetworkError`** β†’ Communication failure (timeout, DNS issue). +* **`InvalidProviderError`** β†’ The requested provider is not supported or not registered. +* **`TransactionNotFoundError`** β†’ Transaction ID does not exist in provider records. +* **`WebhookValidationError`** β†’ Invalid or spoofed webhook payload. +* **`UnsupportedOperationError`** β†’ The provider does not support a requested operation (e.g., cancellation not available). + +```python +try: + status = client.check_status("invalid_id") +except TransactionNotFoundError: + print("Transaction does not exist") +``` + +--- + +## πŸ”Ή Example – Global Error Handling + +```python +from easyswitch.exceptions import * + +try: + client.send_payment(transaction) + +except ConfigurationError as e: + print(f"Bad SDK configuration: {e.details}") + +except AuthenticationError: + print("Authentication failed with provider") + +except RateLimitError: + print("Too many requests, please retry later") + +except PaymentError as e: + print(f"Payment failed: {e.message}, response: {e.details.get('raw_response')}") + +except EasySwitchError as e: + print(f"Unexpected EasySwitch error: {e.message}") + +except Exception as e: + print(f"Unexpected Python error: {str(e)}") +``` + +--- + +## βœ… Best Practices + +* Always catch **specific exceptions** when possible (e.g., `PaymentError`). +* Use `EasySwitchError` as a **generic fallback**. +* Log `details` for debugging (contains raw provider response). +* In production, map exceptions to **user-friendly messages** (e.g., `"Your payment could not be processed, please try again"`). diff --git a/docs/api-reference/shared-types.md b/docs/api-reference/shared-types.md new file mode 100644 index 0000000..9495aa5 --- /dev/null +++ b/docs/api-reference/shared-types.md @@ -0,0 +1,266 @@ +# πŸ“– Shared Types + +The `easyswitch.types` module defines **shared enums, dataclasses, and structures** used across the EasySwitch SDK. +These ensure that all providers, responses, and events follow a consistent format. + +--- + +## 🏦 Providers + +```python +class Provider(str, Enum) +``` + +Represents the list of **supported payment aggregators**. + +| Member | Value | Description | +| ---------- | ------------ | -------------------- | +| `SEMOA` | `"SEMOA"` | Semoa aggregator. | +| `BIZAO` | `"BIZAO"` | Bizao aggregator. | +| `CINETPAY` | `"CINETPAY"` | CinetPay aggregator. | +| `PAYGATE` | `"PAYGATE"` | PayGate aggregator. | +| `FEDAPAY` | `"FEDAPAY"` | FedaPay aggregator. | + +βœ… Used whenever you need to specify or identify the payment provider. + +--- + +## πŸ’± Currency + +```python +class Currency(str, Enum) +``` + +Represents the **supported currencies**. + +| Member | Value | Description | +| ------ | ------- | -------------------------------- | +| `XOF` | `"XOF"` | CFA Franc BCEAO (West Africa). | +| `XAF` | `"XAF"` | CFA Franc BEAC (Central Africa). | +| `NGN` | `"NGN"` | Nigerian Naira. | +| `GHS` | `"GHS"` | Ghanaian Cedi. | +| `EUR` | `"EUR"` | Euro. | +| `USD` | `"USD"` | US Dollar. | +| `CDF` | `"CDF"` | Congolese Franc. | +| `GNF` | `"GNF"` | Guinean Franc. | +| `KMF` | `"KMF"` | Comorian Franc. | + +--- + +## 🌍 Countries + +```python +class Countries(str, Enum) +``` + +Represents the **supported countries**. + +| Member | Value | Description | +| ------------- | ------ | ------------- | +| `TOGO` | `"TG"` | Togo | +| `BENIN` | `"BJ"` | Benin | +| `GHANA` | `"GH"` | Ghana | +| `BURKINA` | `"BF"` | Burkina Faso | +| `IVORY_COAST` | `"CI"` | CΓ΄te d’Ivoire | + +--- + +## πŸ”„ Transaction Types + +```python +class TransactionType(str, Enum) +``` + +Represents the **operation type** of a transaction. + +| Member | Value | Description | +| ------------ | -------------- | --------------------------------------- | +| `PAYMENT` | `"payment"` | Standard payment (customer β†’ merchant). | +| `DEPOSIT` | `"deposit"` | Deposit into a wallet/account. | +| `WITHDRAWAL` | `"withdrawal"` | Withdraw from a wallet/account. | +| `REFUND` | `"refund"` | Refund of a previous transaction. | +| `TRANSFER` | `"transfer"` | Transfer between accounts. | + +--- + +## πŸ“Š Transaction Status + +```python +class TransactionStatus(str, Enum) +``` + +Possible **states** of a transaction. + +| Member | Value | Meaning | +| ------------- | --------------- | ----------------------------------- | +| `PENDING` | `"pending"` | Waiting to be processed. | +| `SUCCESSFUL` | `"successful"` | Completed successfully. | +| `FAILED` | `"failed"` | Failed permanently. | +| `ERROR` | `"error"` | Technical error. | +| `CANCELLED` | `"cancelled"` | Cancelled by user/system. | +| `REFUSED` | `"refused"` | Refused by provider. | +| `DECLINED` | `"declined"` | Declined (e.g. insufficient funds). | +| `EXPIRED` | `"expired"` | Payment expired. | +| `REFUNDED` | `"refunded"` | Transaction refunded. | +| `PROCESSING` | `"processing"` | In progress. | +| `INITIATED` | `"initiated"` | Initiated but not yet processed. | +| `UNKNOWN` | `"unknown"` | Unknown state. | +| `COMPLETED` | `"completed"` | Fully completed. | +| `TRANSFERRED` | `"transferred"` | Successfully transferred. | + +--- + +## πŸ“¦ Data Structures + +### πŸ”Ž `TransactionStatusResponse` + +```python +@dataclass +class TransactionStatusResponse: + transaction_id: str + provider: Provider + status: TransactionStatus + amount: float + data: Dict[str, Any] +``` + +Represents a **standardized status response** from a provider. + +--- + +### πŸ‘€ `CustomerInfo` + +```python +@dataclass +class CustomerInfo: + phone_number: str + first_name: Optional[str] + last_name: Optional[str] + email: Optional[str] + ... +``` + +Represents **customer details** attached to a transaction. + +Useful for receipts, fraud detection, and refunds. + +--- + +### πŸ’³ `PaymentResponse` + +```python +@dataclass +class PaymentResponse: + transaction_id: str + provider: Provider + status: TransactionStatus + amount: float + currency: Currency + ... +``` + +Standardized structure returned after a payment request. + +#### Properties: + +* `is_successful` β†’ `True` if status is `SUCCESSFUL` +* `is_pending` β†’ `True` if status is `PENDING`, `PROCESSING`, or `INITIATED` +* `is_failed` β†’ `True` if status is `FAILED`, `CANCELLED`, or `EXPIRED` + +--- + +### πŸ“‘ `TransactionDetail` + +```python +@dataclass +class TransactionDetail: + transaction_id: str + provider: Provider + amount: float + currency: Currency + status: TransactionStatus + transaction_type: TransactionType + ... +``` + +Represents a **complete record** of a transaction, including metadata, customer info, and timestamps. + +--- + +### πŸ“‘ `WebhookEvent` + +```python +@dataclass +class WebhookEvent: + event_type: str + provider: Provider + transaction_id: str + status: TransactionStatus + amount: float + currency: Currency + ... +``` + +Represents a standardized **webhook notification event**. + +--- + +### πŸ”‘ `ApiCredentials` + +```python +@dataclass +class ApiCredentials: + api_key: str + api_secret: Optional[str] + client_id: Optional[str] + ... +``` + +Represents authentication credentials for a provider. + +#### Utility methods: + +* `load_from_env(provider: Provider)` β†’ Loads credentials from environment variables prefixed with `EASYSWITCH__`. +* `write_to_env(provider: Provider)` β†’ Saves credentials to environment variables. + +βœ… Example: + +```bash +export EASYSWITCH_CINETPAY_API_KEY="pk_test_123" +export EASYSWITCH_CINETPAY_API_SECRET="sk_test_123" +``` + +```python +creds = ApiCredentials(api_key="") +creds.load_from_env(Provider.CINETPAY) +print(creds.api_key) # => pk_test_123 +``` + +--- + +### πŸ“– `PaginationMeta` + +```python +@dataclass +class PaginationMeta: + current_page: int + next_page: Optional[int] + prev_page: Optional[int] + per_page: int + total_pages: int + total_count: int +``` + +Standardized structure used when listing or paginating transactions. + +--- + +## βœ… Summary + +The `easyswitch.types` module provides: + +* Unified enums for **providers, currencies, statuses, and transaction types**. +* Standardized dataclasses for **transactions, customers, payments, webhooks, and pagination**. +* A common **API credentials system** with built-in env helpers. + +These types ensure all providers work seamlessly and consistently under the EasySwitch SDK. diff --git a/docs/assets/javascript/init_kapa_widget.v2.js b/docs/assets/javascript/init_kapa_widget.v2.js index c83cde2..4c5d19c 100644 --- a/docs/assets/javascript/init_kapa_widget.v2.js +++ b/docs/assets/javascript/init_kapa_widget.v2.js @@ -1,31 +1,31 @@ -document.addEventListener("DOMContentLoaded", function () { - var script = document.createElement("script"); - script.src = "https://widget.kapa.ai/kapa-widget.bundle.js"; - script.setAttribute("data-website-id", "95d8dc11-e509-42cb-a7ee-f7e06df52017"); - script.setAttribute("data-project-name", "Atlas"); - script.setAttribute("data-project-color", "#2894F3"); - script.setAttribute("data-project-logo", "https://avatars.githubusercontent.com/u/78708182?s=128"); - script.setAttribute("data-modal-disclaimer", "You can find further support on our [Discord server](https://discord.com/servers/atlas-795710270000332800) or [our GitHub Discussions](https://github.com/Atlas-OS/Atlas/discussions). Remember that not all answers are accurate, as results are AI-generated.") - script.setAttribute("data-modal-example-questions", "Does Atlas support Windows Defender?,Who is Atlas for?,What does Atlas do?") - script.setAttribute("data-modal-example-questions-col-span", "12") - script.async = true; +// document.addEventListener("DOMContentLoaded", function () { +// var script = document.createElement("script"); +// script.src = "https://widget.kapa.ai/kapa-widget.bundle.js"; +// script.setAttribute("data-website-id", "95d8dc11-e509-42cb-a7ee-f7e06df52017"); +// script.setAttribute("data-project-name", "Atlas"); +// script.setAttribute("data-project-color", "#2894F3"); +// script.setAttribute("data-project-logo", "https://avatars.githubusercontent.com/u/78708182?s=128"); +// script.setAttribute("data-modal-disclaimer", "You can find further support on our [Discord server](https://discord.com/servers/atlas-795710270000332800) or [our GitHub Discussions](https://github.com/Atlas-OS/Atlas/discussions). Remember that not all answers are accurate, as results are AI-generated.") +// script.setAttribute("data-modal-example-questions", "Does Atlas support Windows Defender?,Who is Atlas for?,What does Atlas do?") +// script.setAttribute("data-modal-example-questions-col-span", "12") +// script.async = true; - script.onload = () => { - const kapaLoadedCheck = setInterval(() => { - const kapaStyle = document.head.querySelector('style[data-emotion="mantine"]'); - if (kapaStyle && kapaStyle.sheet) { - clearInterval(kapaLoadedCheck); - const cssRules = Array.from(kapaStyle.sheet.cssRules).map(rule => rule.cssText).join('\n'); - const node = document.body.appendChild(kapaStyle); - node.appendChild(document.createTextNode(cssRules)); - } - }, 150); - }; +// script.onload = () => { +// const kapaLoadedCheck = setInterval(() => { +// const kapaStyle = document.head.querySelector('style[data-emotion="mantine"]'); +// if (kapaStyle && kapaStyle.sheet) { +// clearInterval(kapaLoadedCheck); +// const cssRules = Array.from(kapaStyle.sheet.cssRules).map(rule => rule.cssText).join('\n'); +// const node = document.body.appendChild(kapaStyle); +// node.appendChild(document.createTextNode(cssRules)); +// } +// }, 150); +// }; - document.head.appendChild(script); -}); +// document.head.appendChild(script); +// }); -function clickKapaAi() { - const button = document.querySelector('#kapa-widget-container > button') - if (button) button.click() -} +// function clickKapaAi() { +// const button = document.querySelector('#kapa-widget-container > button') +// if (button) button.click() +// } diff --git a/docs/contributing.md b/docs/contributing.md index 45c02e1..5134551 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -2,14 +2,14 @@ Thank you for your interest in contributing to **EasySwitch**! This guide will help you contribute effectively while maintaining our quality standards. -## πŸ“‹ Table of Contents + --- @@ -47,14 +47,14 @@ source venv/bin/activate # Linux/Mac .\venv\Scripts\activate # Windows # Install dependencies -uv pip install -e .[dev] +uv pip install -e . ``` **With standard pip:** ```bash python -m venv venv source venv/bin/activate -pip install -e .[dev] +pip install -e . ``` --- diff --git a/easyswitch/adapters/base.py b/easyswitch/adapters/base.py index f7282f9..10ac129 100644 --- a/easyswitch/adapters/base.py +++ b/easyswitch/adapters/base.py @@ -2,12 +2,11 @@ EasySwitch - Base Adapter for Payment Integrations """ import abc -from typing import Any, ClassVar, Dict, List, Optional, Type, Union +from typing import Any, ClassVar, Dict, List, Optional, Type from easyswitch.conf import ProviderConfig from easyswitch.exceptions import InvalidProviderError -from easyswitch.types import (ApiCredentials, Currency, CustomerInfo, - PaymentResponse, Provider, TransactionDetail, +from easyswitch.types import (Currency, PaymentResponse, TransactionDetail, TransactionStatus) from easyswitch.utils import USER_AGENT from easyswitch.utils.http import HTTPClient @@ -36,7 +35,7 @@ def wrapper(adapter: Type["BaseAdapter"]): nonlocal name name = name or adapter.provider_name() name = name.upper() - if not name in cls._registry.keys(): + if name not in cls._registry.keys(): cls._registry[name] = adapter return adapter diff --git a/easyswitch/integrators/cinetpay.py b/easyswitch/integrators/cinetpay.py index 850689e..cc444d9 100644 --- a/easyswitch/integrators/cinetpay.py +++ b/easyswitch/integrators/cinetpay.py @@ -221,7 +221,10 @@ def parse_webhook(self, payload, headers): raw_data = payload ) - async def send_payment(self, transaction: TransactionDetail) -> PaymentResponse: + async def send_payment( + self, + transaction: TransactionDetail + ) -> PaymentResponse: """ Send a payment request to CinetPay. """ diff --git a/mkdocs.yml b/mkdocs.yml index d7f488c..0d94820 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -81,7 +81,11 @@ nav: - CinetPay: integrations/cinetpay.md - Semoa: integrations/semoa.md - Bizao: integrations/bizao.md - - API Reference: api-reference.md + - API Reference: + - Shared types: api-reference/shared-types.md + - Configuration types: api-reference/config-types.md + - Exceptions: api-reference/exceptions.md + - Base Adapter: api-reference/base-adapter.md - Contributing: contributing.md # French translations @@ -149,7 +153,7 @@ extra: link: https://github.com/AllDotPy/easyswitch name: EasySwitch on GitHub - icon: fontawesome/brands/twitter - link: https://twitter.com/easyswitch + link: https://twitter.com/AllDotPy name: EasySwitch on X \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0299855..df5972c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,5 +52,11 @@ dev = [ [build-system] requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" + +[dependency-groups] +dev = [ + "mkdocs>=1.6.1", + "mkdocs-material>=9.6.14", +] #requires = ["uv"] #build-backend = "uv.build" \ No newline at end of file