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
).
@AdaptersRegistry.register("semoa")
class SemoaAdapter(BaseAdapter):
...
This means you can later do:
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.
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¶
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 <token>" |
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¶
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
andPRODUCTION_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()
.