Skip to content

Commit 3812d55

Browse files
authored
Merge pull request #36 from AllDotPy/feat.docs
Docs: Add API-reference Docs
2 parents d0e95f2 + dbaf624 commit 3812d55

File tree

11 files changed

+928
-40
lines changed

11 files changed

+928
-40
lines changed

docs/api-reference.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/api-reference/base-adapter.md

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
# Base Adapter (`easyswitch.adapters.base`)
2+
3+
This module provides the **foundation of EasySwitch’s adapter system**.
4+
5+
* An **adapter** is a small class that knows how to talk to a specific **payment provider (aggregator)**.
6+
* Each adapter implements the same **common interface**, so that EasySwitch can interact with **any provider** in a consistent way.
7+
* The **adapter registry** keeps track of all registered providers, so you can dynamically load them at runtime.
8+
9+
---
10+
11+
## 🔹 `AdaptersRegistry`
12+
13+
The registry is the **central directory of all adapters**.
14+
Instead of hardcoding adapter classes, EasySwitch lets you **register** and **retrieve** them dynamically.
15+
16+
Think of it as a plugin manager:
17+
18+
* Developers implement an adapter for a new provider.
19+
* They register it under a **provider name**.
20+
* Later, EasySwitch can fetch it by name and use it.
21+
22+
### Methods
23+
24+
#### `AdaptersRegistry.register(name: Optional[str] = None)`
25+
26+
Decorator used to register an adapter under the given name.
27+
28+
* If `name` is provided → adapter is registered under that name.
29+
* If omitted → EasySwitch will use the adapter class name (`SemoaAdapter → semoa`).
30+
31+
```python
32+
@AdaptersRegistry.register("semoa")
33+
class SemoaAdapter(BaseAdapter):
34+
...
35+
```
36+
37+
This means you can later do:
38+
39+
```python
40+
adapter_cls = AdaptersRegistry.get("semoa")
41+
adapter = adapter_cls(config=my_provider_config)
42+
```
43+
44+
---
45+
46+
#### `AdaptersRegistry.get(name: str) -> Type[BaseAdapter]`
47+
48+
Fetches an adapter by name.
49+
50+
* If found → returns the adapter **class** (not an instance).
51+
* If not found → raises `InvalidProviderError`.
52+
53+
---
54+
55+
#### `AdaptersRegistry.all() -> List[Type[BaseAdapter]]`
56+
57+
Returns a list of **all registered adapter classes**.
58+
Useful for debugging or auto-loading providers.
59+
60+
---
61+
62+
#### `AdaptersRegistry.list() -> List[str]`
63+
64+
Returns just the **names** of all registered adapters.
65+
66+
```python
67+
print(AdaptersRegistry.list())
68+
# ["semoa", "wave", "mtn", ...]
69+
```
70+
71+
---
72+
73+
#### `AdaptersRegistry.clear() -> None`
74+
75+
Removes all registered adapters (used in tests).
76+
77+
---
78+
79+
## 🔹 `BaseAdapter`
80+
81+
The **abstract base class** for all adapters.
82+
Every provider must implement this interface to ensure consistency across EasySwitch.
83+
84+
It defines:
85+
86+
* ✅ Common configuration logic (sandbox/production, client setup).
87+
* ✅ Utility methods (validation, required fields, formatting).
88+
* ✅ Abstract methods that **MUST** be implemented per provider.
89+
90+
---
91+
92+
### Class Attributes
93+
94+
* `REQUIRED_FIELDS: List[str]` → List of required fields (ex: `["api_key", "merchant_id"]`).
95+
* `SANDBOX_URL: str` → Provider sandbox base URL.
96+
* `PRODUCTION_URL: str` → Provider production base URL.
97+
* `SUPPORTED_CURRENCIES: List[Currency]` → Currencies supported by the provider.
98+
* `MIN_AMOUNT: Dict[Currency, float]` → Minimum transaction amount per currency.
99+
* `MAX_AMOUNT: Dict[Currency, float]` → Maximum transaction amount per currency.
100+
* `VERSION: str` → Version of the adapter (default `"1.0.0"`).
101+
* `client: Optional[HTTPClient]` → Reusable HTTP client instance.
102+
103+
---
104+
105+
### Constructor
106+
107+
```python
108+
def __init__(self, config: ProviderConfig, context: Optional[Dict[str, Any]] = None)
109+
```
110+
111+
* `config` → Holds provider credentials and environment info (sandbox/production).
112+
* `context` → Optional dict with extra metadata (e.g., debug flags, request ID, etc.).
113+
114+
This constructor is automatically called when you **instantiate** an adapter.
115+
116+
---
117+
118+
### Utility Methods
119+
120+
#### `get_client() -> HTTPClient`
121+
122+
* Ensures an HTTP client is available.
123+
* Reuses the same client for performance.
124+
125+
#### `get_context() -> Dict[str, Any]`
126+
127+
* Returns extra context passed at instantiation.
128+
* Useful for logging, tracing, or debugging.
129+
130+
#### `supports_partial_refund() -> bool`
131+
132+
* Returns `True` if the provider supports **partial refunds**.
133+
* Default: `False`.
134+
135+
#### `provider_name() -> str`
136+
137+
* Returns a **normalized provider name**.
138+
* E.g. `SemoaAdapter``"semoa"`.
139+
140+
---
141+
142+
### Abstract Methods (Must Be Implemented)
143+
144+
Every adapter **must implement** the following methods.
145+
146+
| Method | Purpose | Example Use |
147+
| ---------------------------------------- | ------------------------------------------------ | -------------------------------------- |
148+
| `get_headers(authorization=False)` | Build HTTP headers for requests | Add `"Authorization: Bearer <token>"` |
149+
| `get_credentials()` | Return provider credentials | Used internally to sign requests |
150+
| `send_payment(transaction)` | Send a new payment request | User pays via Semoa/MTN/Wave |
151+
| `check_status(transaction_id)` | Query transaction status | Polling until success/failure |
152+
| `cancel_transaction(transaction_id)` | Cancel a pending transaction | Not all providers support it |
153+
| `get_transaction_detail(transaction_id)` | Get detailed transaction info | Fetch amount, payer, status |
154+
| `refund(transaction_id, amount, reason)` | Process a refund | Full or partial refund |
155+
| `validate_webhook(payload, headers)` | Verify incoming webhook signature | Prevent spoofed requests |
156+
| `parse_webhook(payload, headers)` | Parse provider webhook → EasySwitch format | Normalize webhook events |
157+
| `validate_credentials(credentials)` | Ensure credentials are valid | Check API key correctness |
158+
| `format_transaction(data)` | Convert EasySwitch transaction → provider format | For sending requests |
159+
| `get_normalize_status(status)` | Map provider status → standardized status | `"paid"``TransactionStatus.SUCCESS` |
160+
161+
---
162+
163+
### Validation Methods
164+
165+
#### `get_required_fields() -> List[str]`
166+
167+
Returns the required config fields for this adapter.
168+
169+
#### `validate_transaction(transaction: TransactionDetail) -> bool`
170+
171+
Checks if the transaction is valid:
172+
173+
* Amount within min/max range.
174+
* Currency supported.
175+
* Phone number format valid.
176+
177+
Raises exception if invalid.
178+
179+
---
180+
181+
### URL Resolver
182+
183+
#### `_get_base_url() -> str`
184+
185+
Returns the correct base URL depending on the environment:
186+
187+
* Sandbox → `SANDBOX_URL`.
188+
* Production → `PRODUCTION_URL`.
189+
190+
---
191+
192+
## ✅ Example – Implementing a Custom Adapter
193+
194+
```python
195+
from easyswitch.adapters.base import BaseAdapter, AdaptersRegistry
196+
from easyswitch.types import PaymentResponse, TransactionDetail, TransactionStatus
197+
198+
@AdaptersRegistry.register("semoa")
199+
class SemoaAdapter(BaseAdapter):
200+
SANDBOX_URL = "https://sandbox.semoa.com/api"
201+
PRODUCTION_URL = "https://api.semoa.com"
202+
SUPPORTED_CURRENCIES = ["XOF"]
203+
204+
def get_headers(self, authorization=False):
205+
return {
206+
"Authorization": f"Bearer {self.config.api_key}" if authorization else "",
207+
"Content-Type": "application/json"
208+
}
209+
210+
def get_credentials(self):
211+
return self.config
212+
213+
async def send_payment(self, transaction: TransactionDetail) -> PaymentResponse:
214+
# TODO: Call Semoa API
215+
...
216+
217+
async def check_status(self, transaction_id: str) -> TransactionStatus:
218+
# TODO: Implement status polling
219+
...
220+
221+
async def cancel_transaction(self, transaction_id: str) -> bool:
222+
return False # not supported
223+
224+
async def refund(self, transaction_id: str, amount=None, reason=None) -> PaymentResponse:
225+
...
226+
227+
async def validate_webhook(self, payload, headers) -> bool:
228+
return True
229+
230+
async def parse_webhook(self, payload, headers) -> dict:
231+
return {"status": "parsed"}
232+
233+
def validate_credentials(self, credentials) -> bool:
234+
return bool(credentials.api_key)
235+
```
236+
237+
---
238+
239+
## 📝 Developer Checklist for Writing a New Adapter
240+
241+
Before publishing your adapter, make sure you:
242+
243+
* [ ] Define `SANDBOX_URL` and `PRODUCTION_URL`.
244+
* [ ] Set `SUPPORTED_CURRENCIES`.
245+
* [ ] Implement `send_payment()`.
246+
* [ ] Implement `check_status()`.
247+
* [ ] Implement `refund()` (if supported).
248+
* [ ] Handle webhooks: `validate_webhook()` + `parse_webhook()`.
249+
* [ ] Normalize provider-specific statuses with `get_normalize_status()`.
250+
* [ ] Validate credentials in `validate_credentials()`.
251+
* [ ] Add proper headers in `get_headers()`.

0 commit comments

Comments
 (0)