# SofizPay SDK for Python
<div align="center">
<img src="https://github.com/kenandarabeh/sofizpay-sdk-python/blob/main/assets/sofizpay-logo.png?raw=true" alt="SofizPay Logo" width="200" height="200">
<h3>🚀 A powerful Python SDK for Stellar blockchain payments</h3>
[](https://pypi.org/project/sofizpay-sdk-python/)
[](https://opensource.org/licenses/MIT)
[](https://github.com/kenandarabeh/sofizpay-sdk-python/stargazers)
[](https://github.com/kenandarabeh/sofizpay-sdk-python/issues)
[](https://python.org)
</div>
---
## 📋 Table of Contents
- [Overview](#overview)
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [Usage Examples](#usage-examples)
- [Real-time Transaction Monitoring](#real-time-transaction-monitoring)
- [CIB Transactions](#cib-transactions)
- [Signature Verification](#signature-verification)
- [Error Handling](#error-handling)
- [Best Practices](#best-practices)
- [Contributing](#contributing)
- [Support](#support)
- [License](#license)
---
## 🌟 Overview
SofizPay SDK is a powerful Python library for Stellar blockchain payments with real-time transaction monitoring, comprehensive payment management, and signature verification capabilities.
**Key Benefits:**
- 🔐 Secure Stellar blockchain integration
- ⚡ Real-time transaction monitoring with async support
- 🎯 Simple, intuitive API with type hints
- 📱 Cross-platform Python support (3.8+)
- 🔒 Built-in signature verification
- 🏦 CIB transaction support
---
## ✨ Features
- ✅ **Send Payments**: Secure token transfers with memo support
- ✅ **Transaction History**: Retrieve and filter transaction records
- ✅ **Balance Checking**: Real-time balance queries
- ✅ **Transaction Search**: Find transactions by hash with detailed info
- ✅ **Real-time Streams**: Live transaction monitoring with async callbacks
- ✅ **CIB Transactions**: Create CIB bank transactions for deposits
- ✅ **Signature Verification**: Verify SofizPay signatures and custom signatures
- ✅ **Error Handling**: Robust async error management and reporting
- ✅ **Rate Limiting**: Built-in API rate limiting protection
- ✅ **Type Safety**: Full type hints for better development experience
---
## 📦 Installation
Install SofizPay SDK using pip:
```bash
pip install sofizpay-sdk-python
```
For development with all dependencies:
```bash
pip install sofizpay-sdk-python[dev]
```
### Requirements
- Python 3.8+
- stellar-sdk
- cryptography
- requests
---
## 🚀 Quick Start
### 1. Import the SDK
```python
import asyncio
from sofizpay import SofizPayClient
```
### 2. Initialize the Client
```python
# Initialize with default Stellar mainnet
client = SofizPayClient()
```
### 3. Your First Payment
```python
async def send_payment():
client = SofizPayClient()
result = await client.send_payment(
source_secret="YOUR_SECRET_KEY",
destination_public_key="DESTINATION_PUBLIC_KEY",
amount="10.50",
memo="Payment for services"
)
if result["success"]:
print(f'Payment successful! TX: {result["hash"]}')
else:
print(f'Payment failed: {result["error"]}')
# Run the async function
asyncio.run(send_payment())
```
---
## 📚 API Reference
### Core Payment Operations
#### `send_payment()` - Send Payment
```python
result = await client.send_payment(
source_secret="SECRET_KEY",
destination_public_key="DESTINATION_PUBLIC_KEY",
amount="10.50",
memo="Payment memo" # Optional
)
```
#### `get_balance()` - Check Balance
```python
balance = await client.get_dzt_balance("PUBLIC_KEY")
print(f'Balance: {balance}')
```
#### `get_all_transactions()` - Get Transactions
```python
transactions = await client.get_all_transactions("PUBLIC_KEY", limit=50)
for tx in transactions:
print(f'TX: {tx["hash"]} - Amount: {tx["amount"]}')
```
### Transaction Monitoring
#### `setup_transaction_stream()` - Real-time Monitoring
```python
def handle_transaction(transaction):
print(f'New {transaction["type"]}: {transaction["amount"]}')
stream_id = await client.setup_transaction_stream(
"PUBLIC_KEY",
handle_transaction,
limit=50 # Number of transactions to check for cursor initialization
)
```
**Parameters:**
- `public_key` (str): Public key to monitor for transactions
- `transaction_callback` (Callable): Function to handle new transactions
- `limit` (int, optional): Number of recent transactions to check when setting up the cursor (default: 50)
**Returns:**
- `stream_id` (str): Unique ID for managing the stream
#### `stop_transaction_stream()` - Stop Monitoring
```python
success = client.stop_transaction_stream(stream_id)
```
#### `get_transaction_by_hash()` - Get Transaction Details
```python
result = await client.get_transaction_by_hash("TRANSACTION_HASH")
if result["found"]:
tx = result["transaction"]
print(f'Amount: {tx["amount"]}')
```
### CIB Transactions
#### `make_cib_transaction()` - Create CIB Transaction
```python
result = await client.make_cib_transaction({
"account": "ACCOUNT_NUMBER",
"amount": 100.50,
"full_name": "John Doe",
"phone": "+1234567890",
"email": "john@example.com",
"memo": "Payment for order",
"return_url": "https://your-site.com/callback"
})
```
### Signature Verification
#### `verify_signature()` - Verify Custom Signature
```python
is_valid = client.verify_signature(
message="Hello, world!",
signature="BASE64_SIGNATURE",
)
```
#### `verify_sofizpay_signature()` - Verify SofizPay Signature
```python
is_valid = client.verify_sofizpay_signature({
"message": "wc_order_123success",
"signature_url_safe": "URL_SAFE_BASE64_SIGNATURE"
})
```
### Utility Methods
#### `get_public_key_from_secret()` - Extract Public Key
```python
public_key = client.get_public_key_from_secret("SECRET_KEY")
```
---
## 🔄 Real-time Transaction Monitoring
### Basic Monitoring Setup
```python
import asyncio
from sofizpay import SofizPayClient
async def monitor_transactions():
client = SofizPayClient()
def transaction_handler(transaction):
print(f'🎉 NEW TRANSACTION!')
print(f' Type: {transaction["type"]}')
print(f' Amount: {transaction["amount"]}')
print(f' From: {transaction["from"]}')
print(f' To: {transaction["to"]}')
print(f' Memo: {transaction["memo"]}')
print(f' Time: {transaction["created_at"]}')
# Process the transaction
if transaction["type"] == "received":
handle_incoming_payment(transaction)
# Start monitoring (only NEW transactions after this point)
stream_id = await client.setup_transaction_stream(
"YOUR_PUBLIC_KEY",
transaction_handler,
limit=100 # Check last 100 transactions for cursor initialization
)
print(f"📡 Monitoring started with stream ID: {stream_id}")
# Keep monitoring for 60 seconds
await asyncio.sleep(60)
# Stop monitoring
stopped = client.stop_transaction_stream(stream_id)
print(f"🛑 Monitoring stopped: {stopped}")
def handle_incoming_payment(transaction):
"""Process incoming payments"""
amount = float(transaction["amount"])
if amount >= 10.0:
print(f"✅ Large payment received: {amount}")
# Process large payment...
else:
print(f"💰 Small payment received: {amount}")
# Run monitoring
asyncio.run(monitor_transactions())
```
### Advanced Monitoring with Error Handling
```python
class PaymentMonitor:
def __init__(self):
self.client = SofizPayClient()
self.active_streams = {}
async def start_monitoring(self, public_key: str, limit: int = 50):
"""Start monitoring with error handling"""
try:
stream_id = await self.client.setup_transaction_stream(
public_key,
self._handle_transaction,
limit=limit # Custom limit for cursor initialization
)
self.active_streams[public_key] = stream_id
print(f"✅ Started monitoring for {public_key}")
return stream_id
except Exception as e:
print(f"❌ Failed to start monitoring: {e}")
return None
def _handle_transaction(self, transaction):
"""Handle incoming transactions"""
try:
print(f"📩 New transaction: {transaction['amount']}")
# Your business logic here
except Exception as e:
print(f"❌ Error processing transaction: {e}")
def stop_monitoring(self, public_key: str):
"""Stop monitoring for a specific key"""
if public_key in self.active_streams:
stream_id = self.active_streams[public_key]
success = self.client.stop_transaction_stream(stream_id)
del self.active_streams[public_key]
return success
return False
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
# Stop all active streams
for public_key in list(self.active_streams.keys()):
self.stop_monitoring(public_key)
```
---
## 🏦 CIB Transactions
### Basic CIB Transaction
```python
async def create_cib_transaction():
client = SofizPayClient()
transaction_data = {
"account": "DZ1234567890123456789012",
"amount": 150.75,
"full_name": "Ahmed Mohammed",
"phone": "+213555123456",
"email": "ahmed@example.com",
"memo": "Payment for order #12345",
"return_url": "https://mystore.com/payment/success"
}
try:
result = await client.make_cib_transaction(transaction_data)
if result["success"]:
print("✅ CIB Transaction created successfully!")
print(f"Response: {result['data']}")
print(f"Status: {result['status']}")
else:
print(f"❌ CIB Transaction failed: {result['error']}")
except Exception as e:
print(f"❌ Unexpected error: {e}")
asyncio.run(create_cib_transaction())
```
### CIB Transaction with Full Error Handling
```python
from sofizpay.exceptions import ValidationError, NetworkError
class CIBPaymentProcessor:
def __init__(self):
self.client = SofizPayClient()
async def process_payment(self,
account: str,
amount: float,
customer_name: str,
phone: str,
email: str,
memo: str = None,
return_url: str = None):
"""Process a CIB payment with comprehensive error handling"""
# Validate inputs
if not account or len(account) < 20:
raise ValidationError("Invalid account number")
if amount <= 0:
raise ValidationError("Amount must be positive")
transaction_data = {
"account": account,
"amount": amount,
"full_name": customer_name,
"phone": phone,
"email": email,
"memo": memo or f"Payment from {customer_name}",
"return_url": return_url
}
try:
result = await self.client.make_cib_transaction(transaction_data)
if result["success"]:
return {
"success": True,
"transaction_id": result.get("data", {}).get("id"),
"status": result["status"],
"created_at": result["timestamp"]
}
else:
return {
"success": False,
"error": result["error"],
"status_code": result.get("status_code")
}
except ValidationError as e:
print(f"❌ Validation error: {e}")
raise
except NetworkError as e:
print(f"❌ Network error: {e}")
raise
except Exception as e:
print(f"❌ Unexpected error: {e}")
raise
# Usage
async def main():
processor = CIBPaymentProcessor()
try:
result = await processor.process_payment(
account="DZ1234567890123456789012",
amount=250.00,
customer_name="Sarah Ahmed",
phone="+213777888999",
email="sarah@example.com",
memo="Subscription payment",
return_url="https://myapp.com/success"
)
if result["success"]:
print(f"✅ Payment processed: {result['transaction_id']}")
else:
print(f"❌ Payment failed: {result['error']}")
except Exception as e:
print(f"❌ Error: {e}")
asyncio.run(main())
```
---
## 🔐 Signature Verification
### Verify SofizPay Signatures
```python
def verify_payment_callback(message: str, signature: str) -> bool:
"""Verify a payment callback from SofizPay"""
client = SofizPayClient()
verification_data = {
"message": message,
"signature_url_safe": signature
}
is_valid = client.verify_sofizpay_signature(verification_data)
if is_valid:
print("✅ Signature verified - payment is authentic")
return True
else:
print("❌ Invalid signature - potential fraud")
return False
# Example usage
message = "wc_order_LI3SLQ7xA7IY9cib84907success23400"
signature = "jHrONYl2NuBhjAYTgRq3xwRuW2ZYZIQlx1VWgiObu5F..."
if verify_payment_callback(message, signature):
# Process the verified payment
process_confirmed_payment(message)
```
### Custom Signature Verification
```python
def verify_custom_signature(message: str, signature: str, public_key_pem: str) -> bool:
"""Verify a signature with custom public key"""
client = SofizPayClient()
return client.verify_signature(
message=message,
signature=signature,
public_key=public_key_pem
)
# Example
custom_public_key = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----"""
is_valid = verify_custom_signature("Hello World", "signature_here", custom_public_key)
```
---
## 💡 Usage Examples
### Complete Payment System
```python
import asyncio
from typing import Optional
from sofizpay import SofizPayClient
from sofizpay.exceptions import SofizPayError
class PaymentSystem:
def __init__(self, secret_key: str):
self.client = SofizPayClient()
self.secret_key = secret_key
self.public_key = self.client.get_public_key_from_secret(secret_key)
async def check_balance(self) -> float:
"""Check current balance"""
try:
balance = await self.client.get_dzt_balance(self.public_key)
print(f"💰 Current balance: {balance}")
return balance
except SofizPayError as e:
print(f"❌ Error checking balance: {e}")
return 0.0
async def send_payment(self,
destination: str,
amount: str,
memo: Optional[str] = None) -> bool:
"""Send a payment"""
try:
# Check balance first
balance = await self.check_balance()
if balance < float(amount):
print(f"❌ Insufficient balance: {balance} < {amount}")
return False
# Send payment
result = await self.client.send_payment(
source_secret=self.secret_key,
destination_public_key=destination,
amount=amount,
memo=memo
)
if result["success"]:
print(f"✅ Payment sent successfully!")
print(f" Hash: {result['hash']}")
print(f" Amount: {amount}")
print(f" To: {destination}")
return True
else:
print(f"❌ Payment failed: {result['error']}")
return False
except SofizPayError as e:
print(f"❌ Payment error: {e}")
return False
async def get_transaction_history(self, limit: int = 20):
"""Get recent transactions"""
try:
transactions = await self.client.get_all_transactions(
self.public_key,
limit=limit
)
print(f"📊 Found {len(transactions)} transactions:")
for i, tx in enumerate(transactions[:10], 1):
print(f" {i}. {tx['type'].upper()}: {tx['amount']}")
print(f" Hash: {tx['hash'][:16]}...")
print(f" Date: {tx['created_at']}")
print(f" Memo: {tx['memo'] or 'No memo'}")
print()
return transactions
except SofizPayError as e:
print(f"❌ Error fetching transactions: {e}")
return []
async def start_monitoring(self):
"""Start real-time transaction monitoring"""
def handle_transaction(transaction):
amount = transaction["amount"]
tx_type = transaction["type"]
memo = transaction["memo"]
print(f"🚨 NEW TRANSACTION DETECTED!")
print(f" Type: {tx_type.upper()}")
print(f" Amount: {amount}")
print(f" Memo: {memo or 'No memo'}")
print(f" Time: {transaction['created_at']}")
# Auto-process received payments
if tx_type == "received":
self._process_received_payment(transaction)
try:
stream_id = await self.client.setup_transaction_stream(
self.public_key,
handle_transaction
)
print(f"📡 Started monitoring transactions...")
return stream_id
except SofizPayError as e:
print(f"❌ Error starting monitoring: {e}")
return None
def _process_received_payment(self, transaction):
"""Process incoming payments"""
amount = float(transaction["amount"])
memo = transaction["memo"]
if memo and "order_" in memo:
print(f"🛒 Processing order payment: {amount}")
# Process order...
elif amount >= 100.0:
print(f"💎 Large payment received: {amount}")
# Handle large payment...
else:
print(f"💰 Regular payment received: {amount}")
# Usage example
async def main():
# Initialize payment system
payment_system = PaymentSystem("YOUR_SECRET_KEY")
# Check balance
await payment_system.check_balance()
# Get transaction history
await payment_system.get_transaction_history(limit=10)
# Send a payment
await payment_system.send_payment(
destination="DESTINATION_PUBLIC_KEY",
amount="5.0",
memo="Test payment"
)
# Start monitoring (runs indefinitely)
stream_id = await payment_system.start_monitoring()
# Keep monitoring for 30 seconds
if stream_id:
await asyncio.sleep(30)
payment_system.client.stop_transaction_stream(stream_id)
print("🛑 Monitoring stopped")
# Run the example
asyncio.run(main())
```
### E-commerce Integration Example
```python
from sofizpay import SofizPayClient
from dataclasses import dataclass
from typing import Dict, Any
import asyncio
@dataclass
class Order:
id: str
amount: float
customer_email: str
description: str
status: str = "pending"
class EcommercePaymentGateway:
def __init__(self, merchant_secret_key: str):
self.client = SofizPayClient()
self.merchant_secret = merchant_secret_key
self.merchant_public = self.client.get_public_key_from_secret(merchant_secret_key)
self.pending_orders: Dict[str, Order] = {}
async def create_payment_request(self, order: Order) -> Dict[str, Any]:
"""Create a payment request for an order"""
# Store order
self.pending_orders[order.id] = order
# Start monitoring for this specific payment
await self._start_order_monitoring()
return {
"order_id": order.id,
"payment_address": self.merchant_public,
"amount": order.amount,
"currency": "TOKEN",
"memo": f"order_{order.id}",
"instructions": f"Send exactly {order.amount} to {self.merchant_public} with memo 'order_{order.id}'"
}
async def _start_order_monitoring(self):
"""Monitor for incoming payments"""
def payment_handler(transaction):
memo = transaction.get("memo", "")
if memo.startswith("order_"):
order_id = memo.replace("order_", "")
self._process_order_payment(order_id, transaction)
stream_id = await self.client.setup_transaction_stream(
self.merchant_public,
payment_handler
)
return stream_id
def _process_order_payment(self, order_id: str, transaction: Dict[str, Any]):
"""Process payment for a specific order"""
if order_id not in self.pending_orders:
print(f"❌ Unknown order ID: {order_id}")
return
order = self.pending_orders[order_id]
paid_amount = float(transaction["amount"])
if paid_amount >= order.amount:
order.status = "paid"
print(f"✅ Order {order_id} paid successfully!")
print(f" Amount: {paid_amount}")
print(f" Customer: {order.customer_email}")
# Send confirmation email, fulfill order, etc.
self._fulfill_order(order)
else:
print(f"⚠️ Underpayment for order {order_id}")
print(f" Expected: {order.amount}")
print(f" Received: {paid_amount}")
def _fulfill_order(self, order: Order):
"""Fulfill the paid order"""
print(f"📦 Fulfilling order {order.id}: {order.description}")
# Implement order fulfillment logic
del self.pending_orders[order.id]
# Usage
async def ecommerce_example():
gateway = EcommercePaymentGateway("MERCHANT_SECRET_KEY")
# Create an order
order = Order(
id="ORD-12345",
amount=25.50,
customer_email="customer@example.com",
description="Premium Subscription"
)
# Create payment request
payment_info = await gateway.create_payment_request(order)
print("💳 Payment request created:")
print(f" Order ID: {payment_info['order_id']}")
print(f" Amount: {payment_info['amount']} {payment_info['currency']}")
print(f" Address: {payment_info['payment_address']}")
print(f" Memo: {payment_info['memo']}")
# Keep monitoring for payments
print("📡 Monitoring for payments...")
await asyncio.sleep(60) # Monitor for 1 minute
asyncio.run(ecommerce_example())
```
---
## ⚠️ Error Handling
### Custom Exception Types
```python
from sofizpay.exceptions import (
SofizPayError, # Base exception
PaymentError, # Payment-specific errors
TransactionError, # Transaction-specific errors
NetworkError, # Network-related errors
ValidationError, # Input validation errors
RateLimitError, # Rate limiting errors
InsufficientBalanceError, # Insufficient balance
InvalidAccountError, # Invalid account errors
InvalidAssetError # Invalid asset errors
)
```
### Comprehensive Error Handling
```python
async def robust_payment_handler():
client = SofizPayClient()
try:
result = await client.send_payment(
source_secret="SECRET_KEY",
destination_public_key="DEST_KEY",
amount="10.0",
memo="Test payment"
)
if result["success"]:
print(f"✅ Payment successful: {result['hash']}")
else:
print(f"❌ Payment failed: {result['error']}")
except ValidationError as e:
print(f"❌ Validation error: {e}")
# Handle validation errors (invalid keys, amounts, etc.)
except InsufficientBalanceError as e:
print(f"❌ Insufficient balance: {e}")
# Handle insufficient balance
except NetworkError as e:
print(f"❌ Network error: {e}")
# Handle network issues, retry logic
except RateLimitError as e:
print(f"❌ Rate limit exceeded: {e}")
# Handle rate limiting, implement backoff
except PaymentError as e:
print(f"❌ Payment error: {e}")
# Handle general payment errors
except SofizPayError as e:
print(f"❌ SofizPay error: {e}")
# Handle any SofizPay-related error
except Exception as e:
print(f"❌ Unexpected error: {e}")
# Handle unexpected errors
```
### Async Context Manager
```python
async def safe_payment_operations():
"""Use async context manager for automatic cleanup"""
async with SofizPayClient() as client:
# All operations here
balance = await client.get_dzt_balance("PUBLIC_KEY")
if balance > 10.0:
result = await client.send_payment(
source_secret="SECRET_KEY",
destination_public_key="DEST_KEY",
amount="5.0"
)
print(f"Payment result: {result}")
# Client automatically cleaned up
print("✅ Operations completed and cleaned up")
asyncio.run(safe_payment_operations())
```
---
## 🏆 Best Practices
### Security Best Practices
```python
import os
from cryptography.fernet import Fernet
class SecureConfig:
"""Secure configuration management"""
@staticmethod
def get_secret_key() -> str:
"""Get secret key from environment or secure storage"""
# ✅ Use environment variables
secret_key = os.getenv('SOFIZPAY_SECRET_KEY')
if not secret_key:
raise ValueError("SOFIZPAY_SECRET_KEY not found in environment")
return secret_key
@staticmethod
def encrypt_sensitive_data(data: str, key: bytes) -> str:
"""Encrypt sensitive data"""
f = Fernet(key)
encrypted = f.encrypt(data.encode())
return encrypted.decode()
# ✅ Production usage
async def production_payment():
# Get secret from secure environment
secret_key = SecureConfig.get_secret_key()
client = SofizPayClient()
# Validate before processing using utils
from sofizpay.utils import validate_secret_key
if not validate_secret_key(secret_key):
raise ValueError("Invalid secret key")
# Process payment with error handling
try:
result = await client.send_payment(
source_secret=secret_key,
destination_public_key="DEST_KEY",
amount="10.0"
)
return result
finally:
# Always cleanup
del secret_key
```
### Performance Best Practices
```python
class OptimizedPaymentManager:
def __init__(self):
# ✅ Reuse client instance
self.client = SofizPayClient()
self._balance_cache = {}
self._cache_ttl = 30 # 30 seconds
async def get_cached_balance(self, public_key: str) -> float:
"""Get balance with caching"""
import time
now = time.time()
if public_key in self._balance_cache:
balance, timestamp = self._balance_cache[public_key]
if now - timestamp < self._cache_ttl:
return balance
# Fetch fresh balance
balance = await self.client.get_dzt_balance(public_key)
self._balance_cache[public_key] = (balance, now)
return balance
async def batch_payments(self, payments: list) -> list:
"""Process multiple payments efficiently"""
results = []
# ✅ Use asyncio.gather for concurrent processing
tasks = [
self.client.send_payment(**payment)
for payment in payments
]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
# ✅ Cleanup resources
self._balance_cache.clear()
```
### Validation Best Practices
```python
from typing import Union
import re
class PaymentValidator:
"""Input validation utilities"""
@staticmethod
def validate_amount(amount: Union[str, float]) -> bool:
"""Validate payment amount"""
try:
amount_float = float(amount)
return (
amount_float > 0 and
amount_float <= 922337203685.4775807 and # Stellar limit
len(str(amount).split('.')[-1]) <= 7 # Max 7 decimal places
)
except (ValueError, TypeError):
return False
@staticmethod
def validate_memo(memo: str) -> bool:
"""Validate memo field"""
if not memo:
return True
return len(memo.encode('utf-8')) <= 28 # Stellar memo limit
@staticmethod
def validate_email(email: str) -> bool:
"""Validate email format"""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
@staticmethod
def sanitize_memo(memo: str) -> str:
"""Sanitize memo text"""
if not memo:
return ""
# Remove control characters
sanitized = ''.join(char for char in memo if ord(char) >= 32)
# Truncate if too long
if len(sanitized.encode('utf-8')) > 28:
sanitized = sanitized[:28]
return sanitized
# ✅ Usage
validator = PaymentValidator()
# Validate before sending
if not validator.validate_amount("10.5"):
raise ValueError("Invalid amount")
if not validator.validate_memo("Payment memo"):
raise ValueError("Invalid memo")
# Sanitize inputs
clean_memo = validator.sanitize_memo(user_input_memo)
```
### Development Best Practices
```python
# ✅ Type hints for better IDE support
from typing import Dict, List, Optional, Union, Callable
import logging
# ✅ Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class PaymentApp:
def __init__(self, secret_key: str):
# ✅ Use mainnet for production
self.client = SofizPayClient()
self.secret_key = secret_key
# ✅ Log configuration
logger.info(f"Initialized with mainnet")
async def send_payment_with_retry(self,
destination: str,
amount: str,
max_retries: int = 3) -> Dict:
"""Send payment with retry logic"""
last_error = None
for attempt in range(max_retries):
try:
logger.info(f"Payment attempt {attempt + 1}/{max_retries}")
result = await self.client.send_payment(
source_secret=self.secret_key,
destination_public_key=destination,
amount=amount
)
if result["success"]:
logger.info(f"Payment successful: {result['hash']}")
return result
else:
logger.warning(f"Payment failed: {result['error']}")
last_error = result["error"]
except Exception as e:
logger.error(f"Payment attempt {attempt + 1} failed: {e}")
last_error = str(e)
if attempt < max_retries - 1:
await asyncio.sleep(2 ** attempt) # Exponential backoff
return {"success": False, "error": f"All {max_retries} attempts failed. Last error: {last_error}"}
# ✅ Production usage
async def production_example():
app = PaymentApp(os.getenv('SOFIZPAY_SECRET_KEY'))
result = await app.send_payment_with_retry(
destination="DESTINATION_PUBLIC_KEY",
amount="1.0"
)
print(f"Result: {result}")
asyncio.run(production_example())
```
---
## 🤝 Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
### Development Setup
```bash
# Clone the repository
git clone https://github.com/kenandarabeh/sofizpay-sdk-python.git
cd sofizpay-sdk-python
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt
# Run tests
python -m pytest tests/ -v
# Run linting
flake8 sofizpay/
mypy sofizpay/
# Run formatting
black sofizpay/
isort sofizpay/
```
### Contributing Process
1. **Fork** the repository
2. **Create** feature branch: `git checkout -b feature/amazing-feature`
3. **Make** your changes with tests
4. **Run** tests and linting: `pytest && flake8`
5. **Commit** changes: `git commit -m 'Add amazing feature'`
6. **Push** to branch: `git push origin feature/amazing-feature`
7. **Open** a Pull Request
### Code Standards
- ✅ Follow PEP 8 style guidelines
- ✅ Add type hints to all functions
- ✅ Write comprehensive docstrings
- ✅ Include unit tests for new features
- ✅ Maintain backwards compatibility
- ✅ Update documentation
---
## 📞 Support
- 📖 [Documentation](https://github.com/kenandarabeh/sofizpay-sdk-python#readme)
- 🐛 [Report Issues](https://github.com/kenandarabeh/sofizpay-sdk-python/issues)
- 💬 [Discussions](https://github.com/kenandarabeh/sofizpay-sdk-python/discussions)
- ⭐ [Star the Project](https://github.com/kenandarabeh/sofizpay-sdk-python)
- 📧 [Email Support](mailto:support@sofizpay.com)
### Frequently Asked Questions
**Q: How do I handle rate limiting?**
```python
# The SDK has built-in rate limiting, but you can add custom retry logic
from sofizpay.exceptions import RateLimitError
import asyncio
try:
result = await client.send_payment(...)
except RateLimitError:
await asyncio.sleep(60) # Wait 1 minute
result = await client.send_payment(...) # Retry
```
**Q: Is the SDK thread-safe?**
```python
# Yes, but create separate client instances for different threads
import threading
def worker_thread():
client = SofizPayClient() # Create new instance
# Use client in this thread...
```
---
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
```
MIT License
Copyright (c) 2025 SofizPay
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
---
## 🙏 Acknowledgments
- Built on the robust [Stellar Network](https://stellar.org)
- Powered by [stellar-sdk](https://pypi.org/project/stellar-sdk/)
- Inspired by the growing DeFi ecosystem
- Special thanks to all contributors and the open-source community
### Technical Dependencies
- **stellar-sdk**: Stellar blockchain integration
- **cryptography**: Signature verification and encryption
- **requests**: HTTP client for API calls
- **asyncio**: Asynchronous programming support
---
<div align="center">
<p><strong>Made with ❤️ by the SofizPay Team</strong></p>
<p>
<a href="https://github.com/kenandarabeh/sofizpay-sdk-python">GitHub</a> •
<a href="https://pypi.org/project/sofizpay-sdk-python/">PyPI</a> •
<a href="https://github.com/kenandarabeh/sofizpay-sdk-python/issues">Support</a> •
<a href="mailto:support@sofizpay.com">Contact</a>
</p>
<p>
<img src="https://img.shields.io/badge/Python-3.8+-blue.svg" alt="Python">
<img src="https://img.shields.io/badge/Stellar-Blockchain-brightgreen.svg" alt="Stellar">
</p>
</div>
Raw data
{
"_id": null,
"home_page": "https://github.com/kenandarabeh/sofizpay-sdk-python",
"name": "sofizpay-sdk-python",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "SofizPay Team <support@sofizpay.com>",
"keywords": "stellar, payment, blockchain, cryptocurrency, DZT, sofizpay, fintech",
"author": "SofizPay Team",
"author_email": "SofizPay Team <support@sofizpay.com>",
"download_url": "https://files.pythonhosted.org/packages/98/37/5eedb6833787f45ccb975abf22ac1ee903b73494162586902693642191b6/sofizpay_sdk_python-1.0.1.tar.gz",
"platform": null,
"description": "# SofizPay SDK for Python\n\n<div align=\"center\">\n <img src=\"https://github.com/kenandarabeh/sofizpay-sdk-python/blob/main/assets/sofizpay-logo.png?raw=true\" alt=\"SofizPay Logo\" width=\"200\" height=\"200\">\n \n <h3>\ud83d\ude80 A powerful Python SDK for Stellar blockchain payments</h3>\n \n [](https://pypi.org/project/sofizpay-sdk-python/)\n [](https://opensource.org/licenses/MIT)\n [](https://github.com/kenandarabeh/sofizpay-sdk-python/stargazers)\n [](https://github.com/kenandarabeh/sofizpay-sdk-python/issues)\n [](https://python.org)\n</div>\n\n---\n\n## \ud83d\udccb Table of Contents\n\n- [Overview](#overview)\n- [Features](#features)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [API Reference](#api-reference)\n- [Usage Examples](#usage-examples)\n- [Real-time Transaction Monitoring](#real-time-transaction-monitoring)\n- [CIB Transactions](#cib-transactions)\n- [Signature Verification](#signature-verification)\n- [Error Handling](#error-handling)\n- [Best Practices](#best-practices)\n- [Contributing](#contributing)\n- [Support](#support)\n- [License](#license)\n\n---\n\n## \ud83c\udf1f Overview\n\nSofizPay SDK is a powerful Python library for Stellar blockchain payments with real-time transaction monitoring, comprehensive payment management, and signature verification capabilities.\n\n**Key Benefits:**\n- \ud83d\udd10 Secure Stellar blockchain integration\n- \u26a1 Real-time transaction monitoring with async support\n- \ud83c\udfaf Simple, intuitive API with type hints\n- \ud83d\udcf1 Cross-platform Python support (3.8+)\n- \ud83d\udd12 Built-in signature verification\n- \ud83c\udfe6 CIB transaction support\n\n---\n\n## \u2728 Features\n\n- \u2705 **Send Payments**: Secure token transfers with memo support\n- \u2705 **Transaction History**: Retrieve and filter transaction records \n- \u2705 **Balance Checking**: Real-time balance queries\n- \u2705 **Transaction Search**: Find transactions by hash with detailed info\n- \u2705 **Real-time Streams**: Live transaction monitoring with async callbacks\n- \u2705 **CIB Transactions**: Create CIB bank transactions for deposits\n- \u2705 **Signature Verification**: Verify SofizPay signatures and custom signatures\n- \u2705 **Error Handling**: Robust async error management and reporting\n- \u2705 **Rate Limiting**: Built-in API rate limiting protection\n- \u2705 **Type Safety**: Full type hints for better development experience\n\n---\n\n## \ud83d\udce6 Installation\n\nInstall SofizPay SDK using pip:\n\n```bash\npip install sofizpay-sdk-python\n```\n\nFor development with all dependencies:\n\n```bash\npip install sofizpay-sdk-python[dev]\n```\n\n### Requirements\n\n- Python 3.8+\n- stellar-sdk\n- cryptography\n- requests\n\n---\n\n## \ud83d\ude80 Quick Start\n\n### 1. Import the SDK\n\n```python\nimport asyncio\nfrom sofizpay import SofizPayClient\n```\n\n### 2. Initialize the Client\n\n```python\n# Initialize with default Stellar mainnet\nclient = SofizPayClient()\n```\n\n### 3. Your First Payment\n\n```python\nasync def send_payment():\n client = SofizPayClient()\n \n result = await client.send_payment(\n source_secret=\"YOUR_SECRET_KEY\",\n destination_public_key=\"DESTINATION_PUBLIC_KEY\",\n amount=\"10.50\",\n memo=\"Payment for services\"\n )\n \n if result[\"success\"]:\n print(f'Payment successful! TX: {result[\"hash\"]}')\n else:\n print(f'Payment failed: {result[\"error\"]}')\n\n# Run the async function\nasyncio.run(send_payment())\n```\n\n---\n\n## \ud83d\udcda API Reference\n\n### Core Payment Operations\n\n#### `send_payment()` - Send Payment\n```python\nresult = await client.send_payment(\n source_secret=\"SECRET_KEY\",\n destination_public_key=\"DESTINATION_PUBLIC_KEY\", \n amount=\"10.50\",\n memo=\"Payment memo\" # Optional\n)\n```\n\n#### `get_balance()` - Check Balance\n```python\nbalance = await client.get_dzt_balance(\"PUBLIC_KEY\")\nprint(f'Balance: {balance}')\n```\n\n#### `get_all_transactions()` - Get Transactions\n```python\ntransactions = await client.get_all_transactions(\"PUBLIC_KEY\", limit=50)\nfor tx in transactions:\n print(f'TX: {tx[\"hash\"]} - Amount: {tx[\"amount\"]}')\n```\n\n### Transaction Monitoring\n\n#### `setup_transaction_stream()` - Real-time Monitoring\n```python\ndef handle_transaction(transaction):\n print(f'New {transaction[\"type\"]}: {transaction[\"amount\"]}')\n\nstream_id = await client.setup_transaction_stream(\n \"PUBLIC_KEY\", \n handle_transaction,\n limit=50 # Number of transactions to check for cursor initialization\n)\n```\n\n**Parameters:**\n- `public_key` (str): Public key to monitor for transactions\n- `transaction_callback` (Callable): Function to handle new transactions\n- `limit` (int, optional): Number of recent transactions to check when setting up the cursor (default: 50)\n\n**Returns:**\n- `stream_id` (str): Unique ID for managing the stream\n\n#### `stop_transaction_stream()` - Stop Monitoring\n```python\nsuccess = client.stop_transaction_stream(stream_id)\n```\n\n#### `get_transaction_by_hash()` - Get Transaction Details\n```python\nresult = await client.get_transaction_by_hash(\"TRANSACTION_HASH\")\nif result[\"found\"]:\n tx = result[\"transaction\"]\n print(f'Amount: {tx[\"amount\"]}')\n```\n\n### CIB Transactions\n\n#### `make_cib_transaction()` - Create CIB Transaction\n```python\nresult = await client.make_cib_transaction({\n \"account\": \"ACCOUNT_NUMBER\",\n \"amount\": 100.50,\n \"full_name\": \"John Doe\",\n \"phone\": \"+1234567890\",\n \"email\": \"john@example.com\",\n \"memo\": \"Payment for order\",\n \"return_url\": \"https://your-site.com/callback\"\n})\n```\n\n### Signature Verification\n\n#### `verify_signature()` - Verify Custom Signature\n```python\nis_valid = client.verify_signature(\n message=\"Hello, world!\",\n signature=\"BASE64_SIGNATURE\",\n)\n```\n\n#### `verify_sofizpay_signature()` - Verify SofizPay Signature\n```python\nis_valid = client.verify_sofizpay_signature({\n \"message\": \"wc_order_123success\",\n \"signature_url_safe\": \"URL_SAFE_BASE64_SIGNATURE\"\n})\n```\n\n### Utility Methods\n\n#### `get_public_key_from_secret()` - Extract Public Key\n```python\npublic_key = client.get_public_key_from_secret(\"SECRET_KEY\")\n```\n\n---\n\n## \ud83d\udd04 Real-time Transaction Monitoring\n\n### Basic Monitoring Setup\n\n```python\nimport asyncio\nfrom sofizpay import SofizPayClient\n\nasync def monitor_transactions():\n client = SofizPayClient()\n \n def transaction_handler(transaction):\n print(f'\ud83c\udf89 NEW TRANSACTION!')\n print(f' Type: {transaction[\"type\"]}')\n print(f' Amount: {transaction[\"amount\"]}')\n print(f' From: {transaction[\"from\"]}')\n print(f' To: {transaction[\"to\"]}')\n print(f' Memo: {transaction[\"memo\"]}')\n print(f' Time: {transaction[\"created_at\"]}')\n \n # Process the transaction\n if transaction[\"type\"] == \"received\":\n handle_incoming_payment(transaction)\n \n # Start monitoring (only NEW transactions after this point)\n stream_id = await client.setup_transaction_stream(\n \"YOUR_PUBLIC_KEY\", \n transaction_handler,\n limit=100 # Check last 100 transactions for cursor initialization\n )\n \n print(f\"\ud83d\udce1 Monitoring started with stream ID: {stream_id}\")\n \n # Keep monitoring for 60 seconds\n await asyncio.sleep(60)\n \n # Stop monitoring\n stopped = client.stop_transaction_stream(stream_id)\n print(f\"\ud83d\uded1 Monitoring stopped: {stopped}\")\n\ndef handle_incoming_payment(transaction):\n \"\"\"Process incoming payments\"\"\"\n amount = float(transaction[\"amount\"])\n if amount >= 10.0:\n print(f\"\u2705 Large payment received: {amount}\")\n # Process large payment...\n else:\n print(f\"\ud83d\udcb0 Small payment received: {amount}\")\n\n# Run monitoring\nasyncio.run(monitor_transactions())\n```\n\n### Advanced Monitoring with Error Handling\n\n```python\nclass PaymentMonitor:\n def __init__(self):\n self.client = SofizPayClient()\n self.active_streams = {}\n \n async def start_monitoring(self, public_key: str, limit: int = 50):\n \"\"\"Start monitoring with error handling\"\"\"\n try:\n stream_id = await self.client.setup_transaction_stream(\n public_key, \n self._handle_transaction,\n limit=limit # Custom limit for cursor initialization\n )\n self.active_streams[public_key] = stream_id\n print(f\"\u2705 Started monitoring for {public_key}\")\n return stream_id\n except Exception as e:\n print(f\"\u274c Failed to start monitoring: {e}\")\n return None\n \n def _handle_transaction(self, transaction):\n \"\"\"Handle incoming transactions\"\"\"\n try:\n print(f\"\ud83d\udce9 New transaction: {transaction['amount']}\")\n # Your business logic here\n except Exception as e:\n print(f\"\u274c Error processing transaction: {e}\")\n \n def stop_monitoring(self, public_key: str):\n \"\"\"Stop monitoring for a specific key\"\"\"\n if public_key in self.active_streams:\n stream_id = self.active_streams[public_key]\n success = self.client.stop_transaction_stream(stream_id)\n del self.active_streams[public_key]\n return success\n return False\n \n async def __aenter__(self):\n return self\n \n async def __aexit__(self, exc_type, exc_val, exc_tb):\n # Stop all active streams\n for public_key in list(self.active_streams.keys()):\n self.stop_monitoring(public_key)\n```\n\n---\n\n## \ud83c\udfe6 CIB Transactions\n\n### Basic CIB Transaction\n\n```python\nasync def create_cib_transaction():\n client = SofizPayClient()\n \n transaction_data = {\n \"account\": \"DZ1234567890123456789012\",\n \"amount\": 150.75,\n \"full_name\": \"Ahmed Mohammed\",\n \"phone\": \"+213555123456\",\n \"email\": \"ahmed@example.com\",\n \"memo\": \"Payment for order #12345\",\n \"return_url\": \"https://mystore.com/payment/success\"\n }\n \n try:\n result = await client.make_cib_transaction(transaction_data)\n \n if result[\"success\"]:\n print(\"\u2705 CIB Transaction created successfully!\")\n print(f\"Response: {result['data']}\")\n print(f\"Status: {result['status']}\")\n else:\n print(f\"\u274c CIB Transaction failed: {result['error']}\")\n \n except Exception as e:\n print(f\"\u274c Unexpected error: {e}\")\n\nasyncio.run(create_cib_transaction())\n```\n\n### CIB Transaction with Full Error Handling\n\n```python\nfrom sofizpay.exceptions import ValidationError, NetworkError\n\nclass CIBPaymentProcessor:\n def __init__(self):\n self.client = SofizPayClient()\n \n async def process_payment(self, \n account: str,\n amount: float,\n customer_name: str,\n phone: str,\n email: str,\n memo: str = None,\n return_url: str = None):\n \"\"\"Process a CIB payment with comprehensive error handling\"\"\"\n \n # Validate inputs\n if not account or len(account) < 20:\n raise ValidationError(\"Invalid account number\")\n \n if amount <= 0:\n raise ValidationError(\"Amount must be positive\")\n \n transaction_data = {\n \"account\": account,\n \"amount\": amount,\n \"full_name\": customer_name,\n \"phone\": phone,\n \"email\": email,\n \"memo\": memo or f\"Payment from {customer_name}\",\n \"return_url\": return_url\n }\n \n try:\n result = await self.client.make_cib_transaction(transaction_data)\n \n if result[\"success\"]:\n return {\n \"success\": True,\n \"transaction_id\": result.get(\"data\", {}).get(\"id\"),\n \"status\": result[\"status\"],\n \"created_at\": result[\"timestamp\"]\n }\n else:\n return {\n \"success\": False,\n \"error\": result[\"error\"],\n \"status_code\": result.get(\"status_code\")\n }\n \n except ValidationError as e:\n print(f\"\u274c Validation error: {e}\")\n raise\n except NetworkError as e:\n print(f\"\u274c Network error: {e}\")\n raise\n except Exception as e:\n print(f\"\u274c Unexpected error: {e}\")\n raise\n\n# Usage\nasync def main():\n processor = CIBPaymentProcessor()\n \n try:\n result = await processor.process_payment(\n account=\"DZ1234567890123456789012\",\n amount=250.00,\n customer_name=\"Sarah Ahmed\",\n phone=\"+213777888999\",\n email=\"sarah@example.com\",\n memo=\"Subscription payment\",\n return_url=\"https://myapp.com/success\"\n )\n \n if result[\"success\"]:\n print(f\"\u2705 Payment processed: {result['transaction_id']}\")\n else:\n print(f\"\u274c Payment failed: {result['error']}\")\n \n except Exception as e:\n print(f\"\u274c Error: {e}\")\n\nasyncio.run(main())\n```\n\n---\n\n## \ud83d\udd10 Signature Verification\n\n### Verify SofizPay Signatures\n\n```python\ndef verify_payment_callback(message: str, signature: str) -> bool:\n \"\"\"Verify a payment callback from SofizPay\"\"\"\n client = SofizPayClient()\n \n verification_data = {\n \"message\": message,\n \"signature_url_safe\": signature\n }\n \n is_valid = client.verify_sofizpay_signature(verification_data)\n \n if is_valid:\n print(\"\u2705 Signature verified - payment is authentic\")\n return True\n else:\n print(\"\u274c Invalid signature - potential fraud\")\n return False\n\n# Example usage\nmessage = \"wc_order_LI3SLQ7xA7IY9cib84907success23400\"\nsignature = \"jHrONYl2NuBhjAYTgRq3xwRuW2ZYZIQlx1VWgiObu5F...\"\n\nif verify_payment_callback(message, signature):\n # Process the verified payment\n process_confirmed_payment(message)\n```\n\n### Custom Signature Verification\n\n```python\ndef verify_custom_signature(message: str, signature: str, public_key_pem: str) -> bool:\n \"\"\"Verify a signature with custom public key\"\"\"\n client = SofizPayClient()\n \n return client.verify_signature(\n message=message,\n signature=signature,\n public_key=public_key_pem\n )\n\n# Example\ncustom_public_key = \"\"\"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----\"\"\"\n\nis_valid = verify_custom_signature(\"Hello World\", \"signature_here\", custom_public_key)\n```\n\n---\n\n## \ud83d\udca1 Usage Examples\n\n### Complete Payment System\n\n```python\nimport asyncio\nfrom typing import Optional\nfrom sofizpay import SofizPayClient\nfrom sofizpay.exceptions import SofizPayError\n\nclass PaymentSystem:\n def __init__(self, secret_key: str):\n self.client = SofizPayClient()\n self.secret_key = secret_key\n self.public_key = self.client.get_public_key_from_secret(secret_key)\n \n async def check_balance(self) -> float:\n \"\"\"Check current balance\"\"\"\n try:\n balance = await self.client.get_dzt_balance(self.public_key)\n print(f\"\ud83d\udcb0 Current balance: {balance}\")\n return balance\n except SofizPayError as e:\n print(f\"\u274c Error checking balance: {e}\")\n return 0.0\n \n async def send_payment(self, \n destination: str, \n amount: str, \n memo: Optional[str] = None) -> bool:\n \"\"\"Send a payment\"\"\"\n try:\n # Check balance first\n balance = await self.check_balance()\n if balance < float(amount):\n print(f\"\u274c Insufficient balance: {balance} < {amount}\")\n return False\n \n # Send payment\n result = await self.client.send_payment(\n source_secret=self.secret_key,\n destination_public_key=destination,\n amount=amount,\n memo=memo\n )\n \n if result[\"success\"]:\n print(f\"\u2705 Payment sent successfully!\")\n print(f\" Hash: {result['hash']}\")\n print(f\" Amount: {amount}\")\n print(f\" To: {destination}\")\n return True\n else:\n print(f\"\u274c Payment failed: {result['error']}\")\n return False\n \n except SofizPayError as e:\n print(f\"\u274c Payment error: {e}\")\n return False\n \n async def get_transaction_history(self, limit: int = 20):\n \"\"\"Get recent transactions\"\"\"\n try:\n transactions = await self.client.get_all_transactions(\n self.public_key, \n limit=limit\n )\n \n print(f\"\ud83d\udcca Found {len(transactions)} transactions:\")\n for i, tx in enumerate(transactions[:10], 1):\n print(f\" {i}. {tx['type'].upper()}: {tx['amount']}\")\n print(f\" Hash: {tx['hash'][:16]}...\")\n print(f\" Date: {tx['created_at']}\")\n print(f\" Memo: {tx['memo'] or 'No memo'}\")\n print()\n \n return transactions\n \n except SofizPayError as e:\n print(f\"\u274c Error fetching transactions: {e}\")\n return []\n \n async def start_monitoring(self):\n \"\"\"Start real-time transaction monitoring\"\"\"\n def handle_transaction(transaction):\n amount = transaction[\"amount\"]\n tx_type = transaction[\"type\"]\n memo = transaction[\"memo\"]\n \n print(f\"\ud83d\udea8 NEW TRANSACTION DETECTED!\")\n print(f\" Type: {tx_type.upper()}\")\n print(f\" Amount: {amount}\")\n print(f\" Memo: {memo or 'No memo'}\")\n print(f\" Time: {transaction['created_at']}\")\n \n # Auto-process received payments\n if tx_type == \"received\":\n self._process_received_payment(transaction)\n \n try:\n stream_id = await self.client.setup_transaction_stream(\n self.public_key,\n handle_transaction\n )\n print(f\"\ud83d\udce1 Started monitoring transactions...\")\n return stream_id\n \n except SofizPayError as e:\n print(f\"\u274c Error starting monitoring: {e}\")\n return None\n \n def _process_received_payment(self, transaction):\n \"\"\"Process incoming payments\"\"\"\n amount = float(transaction[\"amount\"])\n memo = transaction[\"memo\"]\n \n if memo and \"order_\" in memo:\n print(f\"\ud83d\uded2 Processing order payment: {amount}\")\n # Process order...\n elif amount >= 100.0:\n print(f\"\ud83d\udc8e Large payment received: {amount}\")\n # Handle large payment...\n else:\n print(f\"\ud83d\udcb0 Regular payment received: {amount}\")\n\n# Usage example\nasync def main():\n # Initialize payment system\n payment_system = PaymentSystem(\"YOUR_SECRET_KEY\")\n \n # Check balance\n await payment_system.check_balance()\n \n # Get transaction history\n await payment_system.get_transaction_history(limit=10)\n \n # Send a payment\n await payment_system.send_payment(\n destination=\"DESTINATION_PUBLIC_KEY\",\n amount=\"5.0\",\n memo=\"Test payment\"\n )\n \n # Start monitoring (runs indefinitely)\n stream_id = await payment_system.start_monitoring()\n \n # Keep monitoring for 30 seconds\n if stream_id:\n await asyncio.sleep(30)\n payment_system.client.stop_transaction_stream(stream_id)\n print(\"\ud83d\uded1 Monitoring stopped\")\n\n# Run the example\nasyncio.run(main())\n```\n\n### E-commerce Integration Example\n\n```python\nfrom sofizpay import SofizPayClient\nfrom dataclasses import dataclass\nfrom typing import Dict, Any\nimport asyncio\n\n@dataclass\nclass Order:\n id: str\n amount: float\n customer_email: str\n description: str\n status: str = \"pending\"\n\nclass EcommercePaymentGateway:\n def __init__(self, merchant_secret_key: str):\n self.client = SofizPayClient()\n self.merchant_secret = merchant_secret_key\n self.merchant_public = self.client.get_public_key_from_secret(merchant_secret_key)\n self.pending_orders: Dict[str, Order] = {}\n \n async def create_payment_request(self, order: Order) -> Dict[str, Any]:\n \"\"\"Create a payment request for an order\"\"\"\n # Store order\n self.pending_orders[order.id] = order\n \n # Start monitoring for this specific payment\n await self._start_order_monitoring()\n \n return {\n \"order_id\": order.id,\n \"payment_address\": self.merchant_public,\n \"amount\": order.amount,\n \"currency\": \"TOKEN\",\n \"memo\": f\"order_{order.id}\",\n \"instructions\": f\"Send exactly {order.amount} to {self.merchant_public} with memo 'order_{order.id}'\"\n }\n \n async def _start_order_monitoring(self):\n \"\"\"Monitor for incoming payments\"\"\"\n def payment_handler(transaction):\n memo = transaction.get(\"memo\", \"\")\n if memo.startswith(\"order_\"):\n order_id = memo.replace(\"order_\", \"\")\n self._process_order_payment(order_id, transaction)\n \n stream_id = await self.client.setup_transaction_stream(\n self.merchant_public,\n payment_handler\n )\n return stream_id\n \n def _process_order_payment(self, order_id: str, transaction: Dict[str, Any]):\n \"\"\"Process payment for a specific order\"\"\"\n if order_id not in self.pending_orders:\n print(f\"\u274c Unknown order ID: {order_id}\")\n return\n \n order = self.pending_orders[order_id]\n paid_amount = float(transaction[\"amount\"])\n \n if paid_amount >= order.amount:\n order.status = \"paid\"\n print(f\"\u2705 Order {order_id} paid successfully!\")\n print(f\" Amount: {paid_amount}\")\n print(f\" Customer: {order.customer_email}\")\n \n # Send confirmation email, fulfill order, etc.\n self._fulfill_order(order)\n else:\n print(f\"\u26a0\ufe0f Underpayment for order {order_id}\")\n print(f\" Expected: {order.amount}\")\n print(f\" Received: {paid_amount}\")\n \n def _fulfill_order(self, order: Order):\n \"\"\"Fulfill the paid order\"\"\"\n print(f\"\ud83d\udce6 Fulfilling order {order.id}: {order.description}\")\n # Implement order fulfillment logic\n del self.pending_orders[order.id]\n\n# Usage\nasync def ecommerce_example():\n gateway = EcommercePaymentGateway(\"MERCHANT_SECRET_KEY\")\n \n # Create an order\n order = Order(\n id=\"ORD-12345\",\n amount=25.50,\n customer_email=\"customer@example.com\",\n description=\"Premium Subscription\"\n )\n \n # Create payment request\n payment_info = await gateway.create_payment_request(order)\n print(\"\ud83d\udcb3 Payment request created:\")\n print(f\" Order ID: {payment_info['order_id']}\")\n print(f\" Amount: {payment_info['amount']} {payment_info['currency']}\")\n print(f\" Address: {payment_info['payment_address']}\")\n print(f\" Memo: {payment_info['memo']}\")\n \n # Keep monitoring for payments\n print(\"\ud83d\udce1 Monitoring for payments...\")\n await asyncio.sleep(60) # Monitor for 1 minute\n\nasyncio.run(ecommerce_example())\n```\n\n---\n\n## \u26a0\ufe0f Error Handling\n\n### Custom Exception Types\n\n```python\nfrom sofizpay.exceptions import (\n SofizPayError, # Base exception\n PaymentError, # Payment-specific errors \n TransactionError, # Transaction-specific errors\n NetworkError, # Network-related errors\n ValidationError, # Input validation errors\n RateLimitError, # Rate limiting errors\n InsufficientBalanceError, # Insufficient balance\n InvalidAccountError, # Invalid account errors\n InvalidAssetError # Invalid asset errors\n)\n```\n\n### Comprehensive Error Handling\n\n```python\nasync def robust_payment_handler():\n client = SofizPayClient()\n \n try:\n result = await client.send_payment(\n source_secret=\"SECRET_KEY\",\n destination_public_key=\"DEST_KEY\",\n amount=\"10.0\",\n memo=\"Test payment\"\n )\n \n if result[\"success\"]:\n print(f\"\u2705 Payment successful: {result['hash']}\")\n else:\n print(f\"\u274c Payment failed: {result['error']}\")\n \n except ValidationError as e:\n print(f\"\u274c Validation error: {e}\")\n # Handle validation errors (invalid keys, amounts, etc.)\n \n except InsufficientBalanceError as e:\n print(f\"\u274c Insufficient balance: {e}\")\n # Handle insufficient balance\n \n except NetworkError as e:\n print(f\"\u274c Network error: {e}\")\n # Handle network issues, retry logic\n \n except RateLimitError as e:\n print(f\"\u274c Rate limit exceeded: {e}\")\n # Handle rate limiting, implement backoff\n \n except PaymentError as e:\n print(f\"\u274c Payment error: {e}\")\n # Handle general payment errors\n \n except SofizPayError as e:\n print(f\"\u274c SofizPay error: {e}\")\n # Handle any SofizPay-related error\n \n except Exception as e:\n print(f\"\u274c Unexpected error: {e}\")\n # Handle unexpected errors\n```\n\n### Async Context Manager\n\n```python\nasync def safe_payment_operations():\n \"\"\"Use async context manager for automatic cleanup\"\"\"\n async with SofizPayClient() as client:\n # All operations here\n balance = await client.get_dzt_balance(\"PUBLIC_KEY\")\n \n if balance > 10.0:\n result = await client.send_payment(\n source_secret=\"SECRET_KEY\",\n destination_public_key=\"DEST_KEY\", \n amount=\"5.0\"\n )\n print(f\"Payment result: {result}\")\n \n # Client automatically cleaned up\n print(\"\u2705 Operations completed and cleaned up\")\n\nasyncio.run(safe_payment_operations())\n```\n\n---\n\n## \ud83c\udfc6 Best Practices\n\n### Security Best Practices\n\n```python\nimport os\nfrom cryptography.fernet import Fernet\n\nclass SecureConfig:\n \"\"\"Secure configuration management\"\"\"\n \n @staticmethod\n def get_secret_key() -> str:\n \"\"\"Get secret key from environment or secure storage\"\"\"\n # \u2705 Use environment variables\n secret_key = os.getenv('SOFIZPAY_SECRET_KEY')\n if not secret_key:\n raise ValueError(\"SOFIZPAY_SECRET_KEY not found in environment\")\n return secret_key\n \n @staticmethod\n def encrypt_sensitive_data(data: str, key: bytes) -> str:\n \"\"\"Encrypt sensitive data\"\"\"\n f = Fernet(key)\n encrypted = f.encrypt(data.encode())\n return encrypted.decode()\n\n# \u2705 Production usage\nasync def production_payment():\n # Get secret from secure environment\n secret_key = SecureConfig.get_secret_key()\n \n client = SofizPayClient()\n \n # Validate before processing using utils\n from sofizpay.utils import validate_secret_key\n if not validate_secret_key(secret_key):\n raise ValueError(\"Invalid secret key\")\n \n # Process payment with error handling\n try:\n result = await client.send_payment(\n source_secret=secret_key,\n destination_public_key=\"DEST_KEY\",\n amount=\"10.0\"\n )\n return result\n finally:\n # Always cleanup\n del secret_key\n```\n\n### Performance Best Practices\n\n```python\nclass OptimizedPaymentManager:\n def __init__(self):\n # \u2705 Reuse client instance\n self.client = SofizPayClient()\n self._balance_cache = {}\n self._cache_ttl = 30 # 30 seconds\n \n async def get_cached_balance(self, public_key: str) -> float:\n \"\"\"Get balance with caching\"\"\"\n import time\n now = time.time()\n \n if public_key in self._balance_cache:\n balance, timestamp = self._balance_cache[public_key]\n if now - timestamp < self._cache_ttl:\n return balance\n \n # Fetch fresh balance\n balance = await self.client.get_dzt_balance(public_key)\n self._balance_cache[public_key] = (balance, now)\n return balance\n \n async def batch_payments(self, payments: list) -> list:\n \"\"\"Process multiple payments efficiently\"\"\"\n results = []\n \n # \u2705 Use asyncio.gather for concurrent processing\n tasks = [\n self.client.send_payment(**payment) \n for payment in payments\n ]\n \n results = await asyncio.gather(*tasks, return_exceptions=True)\n return results\n \n async def __aenter__(self):\n return self\n \n async def __aexit__(self, exc_type, exc_val, exc_tb):\n # \u2705 Cleanup resources\n self._balance_cache.clear()\n```\n\n### Validation Best Practices\n\n```python\nfrom typing import Union\nimport re\n\nclass PaymentValidator:\n \"\"\"Input validation utilities\"\"\"\n \n @staticmethod\n def validate_amount(amount: Union[str, float]) -> bool:\n \"\"\"Validate payment amount\"\"\"\n try:\n amount_float = float(amount)\n return (\n amount_float > 0 and \n amount_float <= 922337203685.4775807 and # Stellar limit\n len(str(amount).split('.')[-1]) <= 7 # Max 7 decimal places\n )\n except (ValueError, TypeError):\n return False\n \n @staticmethod \n def validate_memo(memo: str) -> bool:\n \"\"\"Validate memo field\"\"\"\n if not memo:\n return True\n return len(memo.encode('utf-8')) <= 28 # Stellar memo limit\n \n @staticmethod\n def validate_email(email: str) -> bool:\n \"\"\"Validate email format\"\"\"\n pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n return bool(re.match(pattern, email))\n \n @staticmethod\n def sanitize_memo(memo: str) -> str:\n \"\"\"Sanitize memo text\"\"\"\n if not memo:\n return \"\"\n \n # Remove control characters\n sanitized = ''.join(char for char in memo if ord(char) >= 32)\n \n # Truncate if too long\n if len(sanitized.encode('utf-8')) > 28:\n sanitized = sanitized[:28]\n \n return sanitized\n\n# \u2705 Usage\nvalidator = PaymentValidator()\n\n# Validate before sending\nif not validator.validate_amount(\"10.5\"):\n raise ValueError(\"Invalid amount\")\n\nif not validator.validate_memo(\"Payment memo\"):\n raise ValueError(\"Invalid memo\")\n\n# Sanitize inputs\nclean_memo = validator.sanitize_memo(user_input_memo)\n```\n\n### Development Best Practices\n\n```python\n# \u2705 Type hints for better IDE support\nfrom typing import Dict, List, Optional, Union, Callable\nimport logging\n\n# \u2705 Configure logging\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(__name__)\n\nclass PaymentApp:\n def __init__(self, secret_key: str):\n # \u2705 Use mainnet for production\n self.client = SofizPayClient()\n self.secret_key = secret_key\n \n # \u2705 Log configuration\n logger.info(f\"Initialized with mainnet\")\n \n async def send_payment_with_retry(self, \n destination: str, \n amount: str,\n max_retries: int = 3) -> Dict:\n \"\"\"Send payment with retry logic\"\"\"\n last_error = None\n \n for attempt in range(max_retries):\n try:\n logger.info(f\"Payment attempt {attempt + 1}/{max_retries}\")\n \n result = await self.client.send_payment(\n source_secret=self.secret_key,\n destination_public_key=destination,\n amount=amount\n )\n \n if result[\"success\"]:\n logger.info(f\"Payment successful: {result['hash']}\")\n return result\n else:\n logger.warning(f\"Payment failed: {result['error']}\")\n last_error = result[\"error\"]\n \n except Exception as e:\n logger.error(f\"Payment attempt {attempt + 1} failed: {e}\")\n last_error = str(e)\n \n if attempt < max_retries - 1:\n await asyncio.sleep(2 ** attempt) # Exponential backoff\n \n return {\"success\": False, \"error\": f\"All {max_retries} attempts failed. Last error: {last_error}\"}\n\n# \u2705 Production usage\nasync def production_example():\n app = PaymentApp(os.getenv('SOFIZPAY_SECRET_KEY'))\n \n result = await app.send_payment_with_retry(\n destination=\"DESTINATION_PUBLIC_KEY\",\n amount=\"1.0\"\n )\n \n print(f\"Result: {result}\")\n\nasyncio.run(production_example())\n```\n\n---\n\n## \ud83e\udd1d Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n### Development Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/kenandarabeh/sofizpay-sdk-python.git\ncd sofizpay-sdk-python\n\n# Create virtual environment\npython -m venv venv\nsource venv/bin/activate # On Windows: venv\\Scripts\\activate\n\n# Install dependencies\npip install -r requirements.txt\npip install -r requirements-dev.txt\n\n# Run tests\npython -m pytest tests/ -v\n\n# Run linting\nflake8 sofizpay/\nmypy sofizpay/\n\n# Run formatting\nblack sofizpay/\nisort sofizpay/\n```\n\n### Contributing Process\n\n1. **Fork** the repository\n2. **Create** feature branch: `git checkout -b feature/amazing-feature`\n3. **Make** your changes with tests\n4. **Run** tests and linting: `pytest && flake8`\n5. **Commit** changes: `git commit -m 'Add amazing feature'`\n6. **Push** to branch: `git push origin feature/amazing-feature`\n7. **Open** a Pull Request\n\n### Code Standards\n\n- \u2705 Follow PEP 8 style guidelines\n- \u2705 Add type hints to all functions\n- \u2705 Write comprehensive docstrings\n- \u2705 Include unit tests for new features\n- \u2705 Maintain backwards compatibility\n- \u2705 Update documentation\n\n---\n\n## \ud83d\udcde Support\n\n- \ud83d\udcd6 [Documentation](https://github.com/kenandarabeh/sofizpay-sdk-python#readme)\n- \ud83d\udc1b [Report Issues](https://github.com/kenandarabeh/sofizpay-sdk-python/issues)\n- \ud83d\udcac [Discussions](https://github.com/kenandarabeh/sofizpay-sdk-python/discussions)\n- \u2b50 [Star the Project](https://github.com/kenandarabeh/sofizpay-sdk-python)\n- \ud83d\udce7 [Email Support](mailto:support@sofizpay.com)\n\n### Frequently Asked Questions\n\n**Q: How do I handle rate limiting?**\n```python\n# The SDK has built-in rate limiting, but you can add custom retry logic\nfrom sofizpay.exceptions import RateLimitError\nimport asyncio\n\ntry:\n result = await client.send_payment(...)\nexcept RateLimitError:\n await asyncio.sleep(60) # Wait 1 minute\n result = await client.send_payment(...) # Retry\n```\n\n**Q: Is the SDK thread-safe?**\n```python\n# Yes, but create separate client instances for different threads\nimport threading\n\ndef worker_thread():\n client = SofizPayClient() # Create new instance\n # Use client in this thread...\n```\n\n---\n\n## \ud83d\udcc4 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n```\nMIT License\n\nCopyright (c) 2025 SofizPay\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n\n---\n\n## \ud83d\ude4f Acknowledgments\n\n- Built on the robust [Stellar Network](https://stellar.org)\n- Powered by [stellar-sdk](https://pypi.org/project/stellar-sdk/)\n- Inspired by the growing DeFi ecosystem\n- Special thanks to all contributors and the open-source community\n\n### Technical Dependencies\n\n- **stellar-sdk**: Stellar blockchain integration\n- **cryptography**: Signature verification and encryption\n- **requests**: HTTP client for API calls\n- **asyncio**: Asynchronous programming support\n\n---\n\n<div align=\"center\">\n <p><strong>Made with \u2764\ufe0f by the SofizPay Team</strong></p>\n <p>\n <a href=\"https://github.com/kenandarabeh/sofizpay-sdk-python\">GitHub</a> \u2022\n <a href=\"https://pypi.org/project/sofizpay-sdk-python/\">PyPI</a> \u2022\n <a href=\"https://github.com/kenandarabeh/sofizpay-sdk-python/issues\">Support</a> \u2022\n <a href=\"mailto:support@sofizpay.com\">Contact</a>\n </p>\n \n <p>\n <img src=\"https://img.shields.io/badge/Python-3.8+-blue.svg\" alt=\"Python\">\n <img src=\"https://img.shields.io/badge/Stellar-Blockchain-brightgreen.svg\" alt=\"Stellar\">\n </p>\n</div>\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Professional Python SDK for SofizPay payments using Stellar blockchain",
"version": "1.0.1",
"project_urls": {
"Bug Reports": "https://github.com/kenandarabeh/sofizpay-sdk-python/issues",
"Changelog": "https://github.com/kenandarabeh/sofizpay-sdk-python/blob/main/CHANGELOG.md",
"Documentation": "https://github.com/kenandarabeh/sofizpay-sdk-python#readme",
"Homepage": "https://sofizpay.com",
"Repository": "https://github.com/kenandarabeh/sofizpay-sdk-python"
},
"split_keywords": [
"stellar",
" payment",
" blockchain",
" cryptocurrency",
" dzt",
" sofizpay",
" fintech"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "d5ac17c72bb65c922a24d6fc3898f5e8b3e06fbc03f7adea439c227ef5293ed4",
"md5": "daf605d289a84f5926dcb295f1ed48c0",
"sha256": "59fc08cab7b0a4fb26e0891ef04930fa352187cf34b0c7e0fb458925c67a3b72"
},
"downloads": -1,
"filename": "sofizpay_sdk_python-1.0.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "daf605d289a84f5926dcb295f1ed48c0",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 25809,
"upload_time": "2025-07-17T17:43:52",
"upload_time_iso_8601": "2025-07-17T17:43:52.736681Z",
"url": "https://files.pythonhosted.org/packages/d5/ac/17c72bb65c922a24d6fc3898f5e8b3e06fbc03f7adea439c227ef5293ed4/sofizpay_sdk_python-1.0.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "98375eedb6833787f45ccb975abf22ac1ee903b73494162586902693642191b6",
"md5": "93251beef9dafdce5314e81ef15e5b43",
"sha256": "1c7df8789fd17b2b5a69897e8711835c689ec9d9deae84ff23458a4200138f3a"
},
"downloads": -1,
"filename": "sofizpay_sdk_python-1.0.1.tar.gz",
"has_sig": false,
"md5_digest": "93251beef9dafdce5314e81ef15e5b43",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 797530,
"upload_time": "2025-07-17T17:44:41",
"upload_time_iso_8601": "2025-07-17T17:44:41.404971Z",
"url": "https://files.pythonhosted.org/packages/98/37/5eedb6833787f45ccb975abf22ac1ee903b73494162586902693642191b6/sofizpay_sdk_python-1.0.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-17 17:44:41",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "kenandarabeh",
"github_project": "sofizpay-sdk-python",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "stellar-sdk",
"specs": [
[
">=",
"8.0.0"
]
]
},
{
"name": "requests",
"specs": [
[
">=",
"2.25.0"
]
]
},
{
"name": "asyncio-throttle",
"specs": [
[
">=",
"1.0.0"
]
]
},
{
"name": "websockets",
"specs": [
[
">=",
"10.0"
]
]
},
{
"name": "cryptography",
"specs": [
[
">=",
"3.4.0"
]
]
},
{
"name": "pycryptodome",
"specs": [
[
">=",
"3.15.0"
]
]
}
],
"lcname": "sofizpay-sdk-python"
}