Initial Python SDK release v1.0.0
This commit is contained in:
153
fetcherpay/client.py
Normal file
153
fetcherpay/client.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""
|
||||
FetcherPay API Client
|
||||
"""
|
||||
|
||||
import hmac
|
||||
import hashlib
|
||||
from typing import Optional
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from .exceptions import (
|
||||
FetcherPayError,
|
||||
AuthenticationError,
|
||||
ValidationError,
|
||||
NotFoundError,
|
||||
IdempotencyError,
|
||||
)
|
||||
from .api.payments import PaymentsAPI
|
||||
from .api.ledger import LedgerAPI
|
||||
from .api.payment_methods import PaymentMethodsAPI
|
||||
from .api.webhooks import WebhooksAPI
|
||||
|
||||
|
||||
class FetcherPay:
|
||||
"""
|
||||
Main FetcherPay client
|
||||
|
||||
Args:
|
||||
api_key: Your FetcherPay API key
|
||||
environment: 'sandbox' or 'production'
|
||||
base_url: Optional custom base URL
|
||||
timeout: Request timeout in seconds (default: 30)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: str,
|
||||
environment: str = 'sandbox',
|
||||
base_url: Optional[str] = None,
|
||||
timeout: int = 30
|
||||
):
|
||||
self.api_key = api_key
|
||||
self.environment = environment
|
||||
self.timeout = timeout
|
||||
|
||||
if base_url:
|
||||
self.base_url = base_url
|
||||
elif environment == 'production':
|
||||
self.base_url = 'https://api.fetcherpay.com/v1'
|
||||
else:
|
||||
self.base_url = 'https://sandbox.fetcherpay.com/v1'
|
||||
|
||||
# Create session with retries
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
'Authorization': f'Bearer {api_key}',
|
||||
'Content-Type': 'application/json',
|
||||
})
|
||||
|
||||
# Initialize API modules
|
||||
self.payments = PaymentsAPI(self)
|
||||
self.ledger = LedgerAPI(self)
|
||||
self.payment_methods = PaymentMethodsAPI(self)
|
||||
self.webhooks = WebhooksAPI(self)
|
||||
|
||||
def _request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
params: Optional[dict] = None,
|
||||
json: Optional[dict] = None,
|
||||
headers: Optional[dict] = None,
|
||||
idempotency_key: Optional[str] = None
|
||||
) -> dict:
|
||||
"""Make an HTTP request"""
|
||||
url = urljoin(self.base_url, path)
|
||||
|
||||
request_headers = {}
|
||||
if headers:
|
||||
request_headers.update(headers)
|
||||
if idempotency_key:
|
||||
request_headers['Idempotency-Key'] = idempotency_key
|
||||
|
||||
try:
|
||||
response = self.session.request(
|
||||
method=method,
|
||||
url=url,
|
||||
params=params,
|
||||
json=json,
|
||||
headers=request_headers,
|
||||
timeout=self.timeout
|
||||
)
|
||||
|
||||
# Handle errors
|
||||
if response.status_code >= 400:
|
||||
self._handle_error(response)
|
||||
|
||||
return response.json() if response.content else {}
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
raise FetcherPayError('Request timeout', 'timeout_error')
|
||||
except requests.exceptions.ConnectionError:
|
||||
raise FetcherPayError('Connection error', 'connection_error')
|
||||
|
||||
def _handle_error(self, response):
|
||||
"""Handle API errors"""
|
||||
try:
|
||||
data = response.json()
|
||||
error = data.get('error', {})
|
||||
except:
|
||||
error = {}
|
||||
|
||||
message = error.get('message', 'An error occurred')
|
||||
error_type = error.get('type', 'api_error')
|
||||
param = error.get('param')
|
||||
code = error.get('code')
|
||||
|
||||
if response.status_code == 401:
|
||||
raise AuthenticationError(message)
|
||||
elif response.status_code == 404:
|
||||
raise NotFoundError(message)
|
||||
elif response.status_code == 422:
|
||||
raise ValidationError(message, param)
|
||||
elif response.status_code == 409:
|
||||
raise IdempotencyError(message)
|
||||
else:
|
||||
raise FetcherPayError(message, error_type, response.status_code, param, code)
|
||||
|
||||
def verify_webhook_signature(
|
||||
self,
|
||||
payload: str,
|
||||
signature: str,
|
||||
secret: str
|
||||
) -> bool:
|
||||
"""
|
||||
Verify a webhook signature
|
||||
|
||||
Args:
|
||||
payload: The raw webhook payload string
|
||||
signature: The signature from X-FetcherPay-Signature header
|
||||
secret: Your webhook secret
|
||||
|
||||
Returns:
|
||||
True if signature is valid
|
||||
"""
|
||||
expected = hmac.new(
|
||||
secret.encode('utf-8'),
|
||||
payload.encode('utf-8'),
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
return hmac.compare_digest(signature, expected)
|
||||
Reference in New Issue
Block a user