Python SDK
The Azotte Python SDK provides a comprehensive interface for integrating subscription billing and payment processing into Python applications.
Installation
Using pip
pip install azotte-python
Using conda
conda install -c azotte azotte-python
From source
git clone https://github.com/azotte/azotte-python.git
cd azotte-python
pip install -e .
Quick Start
Basic Setup
import azotte
from azotte import Customer, Subscription, Plan
# Initialize the client
client = azotte.Client(
api_key='sk_live_...', # Your secret key
tenant_id='tenant_123', # Optional: multi-tenant setup
environment='production' # or 'sandbox'
)
# Create a customer
customer = client.customers.create(
name='John Doe',
email='john@example.com',
phone='+1234567890'
)
# Create a subscription
subscription = client.subscriptions.create(
customer_id=customer.id,
plan_id='plan_basic_monthly',
payment_method='pm_card_visa'
)
print(f"Subscription created: {subscription.id}")
Configuration
Environment Variables
import os
from azotte import Client
# Load from environment variables
client = Client(
api_key=os.getenv('AZOTTE_API_KEY'),
tenant_id=os.getenv('AZOTTE_TENANT_ID'),
webhook_secret=os.getenv('AZOTTE_WEBHOOK_SECRET')
)
Advanced Configuration
from azotte import Client, Config
config = Config(
api_key='sk_live_...',
base_url='https://api.azotte.com', # Custom endpoint
timeout=30, # Request timeout in seconds
retries=3, # Number of retry attempts
backoff_factor=2, # Exponential backoff factor
verify_ssl=True, # SSL certificate verification
user_agent='MyApp/1.0' # Custom user agent
)
client = Client(config)
Customer Management
Create Customer
from azotte.models import Customer
customer = client.customers.create(
name='Jane Smith',
email='jane@example.com',
phone='+1987654321',
metadata={
'source': 'website',
'campaign': 'summer_2024'
},
address={
'line1': '123 Main St',
'city': 'San Francisco',
'state': 'CA',
'postal_code': '94105',
'country': 'US'
}
)
Retrieve Customer
# Get customer by ID
customer = client.customers.retrieve('cus_123')
# Get customer with expanded data
customer = client.customers.retrieve(
'cus_123',
expand=['subscriptions', 'payment_methods']
)
print(f"Customer: {customer.name}")
print(f"Subscriptions: {len(customer.subscriptions)}")
Update Customer
customer = client.customers.update(
'cus_123',
name='Jane Doe',
email='jane.doe@example.com',
metadata={'updated_at': '2024-01-15'}
)
List Customers
# Basic listing
customers = client.customers.list(limit=10)
# With filters
customers = client.customers.list(
created_after='2024-01-01',
email_contains='@company.com',
has_subscription=True,
limit=50
)
# Pagination
for customer in client.customers.auto_paging_iter():
print(f"Processing customer: {customer.id}")
Subscription Management
Create Subscription
from azotte.models import Subscription
from datetime import datetime, timedelta
subscription = client.subscriptions.create(
customer_id='cus_123',
plan_id='plan_premium_annual',
trial_period_days=14,
start_date=datetime.now() + timedelta(days=7),
payment_method='pm_card_123',
metadata={'promo_code': 'WELCOME20'}
)
Subscription with Custom Pricing
subscription = client.subscriptions.create(
customer_id='cus_123',
items=[
{
'plan_id': 'plan_basic',
'quantity': 1
},
{
'plan_id': 'addon_analytics',
'quantity': 1
}
],
discount_coupon='SAVE10',
billing_cycle_anchor='now'
)
Update Subscription
# Change plan
subscription = client.subscriptions.update(
'sub_123',
plan_id='plan_enterprise',
proration_behavior='create_prorations'
)
# Pause subscription
subscription = client.subscriptions.pause('sub_123')
# Resume subscription
subscription = client.subscriptions.resume('sub_123')
# Cancel subscription
subscription = client.subscriptions.cancel(
'sub_123',
at_period_end=True,
cancellation_reason='customer_request'
)
Payment Processing
Payment Methods
# Add payment method
payment_method = client.payment_methods.create(
customer_id='cus_123',
type='card',
card={
'number': '4111111111111111',
'exp_month': 12,
'exp_year': 2025,
'cvc': '123'
}
)
# Set default payment method
client.customers.update(
'cus_123',
default_payment_method=payment_method.id
)
One-time Payments
from azotte.models import Payment
payment = client.payments.create(
amount=2999, # $29.99
currency='usd',
customer_id='cus_123',
payment_method='pm_123',
description='One-time setup fee',
confirm=True
)
if payment.status == 'succeeded':
print(f"Payment successful: {payment.id}")
else:
print(f"Payment failed: {payment.failure_reason}")
Refunds
# Full refund
refund = client.refunds.create(
payment_id='pay_123',
reason='requested_by_customer'
)
# Partial refund
refund = client.refunds.create(
payment_id='pay_123',
amount=1500, # $15.00
reason='defective_product'
)
Webhook Handling
Flask Integration
from flask import Flask, request, jsonify
import azotte
app = Flask(__name__)
client = azotte.Client(api_key='sk_live_...')
@app.route('/webhooks/azotte', methods=['POST'])
def handle_webhook():
payload = request.get_data()
signature = request.headers.get('azotte-signature')
try:
event = client.webhooks.construct_event(
payload, signature, 'whsec_...'
)
except azotte.SignatureVerificationError:
return jsonify({'error': 'Invalid signature'}), 400
# Handle the event
if event.type == 'subscription.created':
handle_subscription_created(event.data)
elif event.type == 'payment.succeeded':
handle_payment_succeeded(event.data)
elif event.type == 'payment.failed':
handle_payment_failed(event.data)
return jsonify({'status': 'success'})
def handle_subscription_created(subscription):
print(f"New subscription: {subscription.id}")
# Send welcome email, grant access, etc.
def handle_payment_succeeded(payment):
print(f"Payment received: ${payment.amount / 100}")
# Update user account, send receipt, etc.
def handle_payment_failed(payment):
print(f"Payment failed: {payment.failure_reason}")
# Send dunning email, suspend access, etc.
Django Integration
# views.py
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
import azotte
import json
client = azotte.Client(api_key=settings.AZOTTE_API_KEY)
@csrf_exempt
@require_POST
def azotte_webhook(request):
payload = request.body
signature = request.META.get('HTTP_AZOTTE_SIGNATURE')
try:
event = client.webhooks.construct_event(
payload, signature, settings.AZOTTE_WEBHOOK_SECRET
)
except azotte.SignatureVerificationError:
return HttpResponse(status=400)
# Process event asynchronously with Celery
process_azotte_event.delay(event.to_dict())
return HttpResponse(status=200)
# tasks.py (Celery)
from celery import shared_task
@shared_task
def process_azotte_event(event_data):
event_type = event_data['type']
data = event_data['data']
if event_type == 'subscription.updated':
# Update local subscription record
update_subscription_status(data['id'], data['status'])
Advanced Features
Metered Billing
# Report usage
usage_record = client.subscription_items.create_usage_record(
subscription_item_id='si_123',
quantity=100,
timestamp=datetime.now(),
action='increment' # or 'set'
)
# Get usage summary
usage_summary = client.subscription_items.list_usage_records(
subscription_item_id='si_123',
start_date='2024-01-01',
end_date='2024-01-31'
)
Invoicing
# Create invoice
invoice = client.invoices.create(
customer_id='cus_123',
auto_advance=True,
collection_method='charge_automatically'
)
# Add invoice items
client.invoice_items.create(
customer_id='cus_123',
amount=2500,
currency='usd',
description='Professional services'
)
# Finalize and send
invoice = client.invoices.finalize('in_123')
client.invoices.send(invoice.id)
Disputes and Chargebacks
# List disputes
disputes = client.disputes.list(status='needs_response')
# Submit evidence
client.disputes.submit_evidence(
'dp_123',
evidence={
'customer_communication': 'Email thread with customer',
'receipt': 'Payment receipt #12345',
'shipping_documentation': 'Tracking number: 1Z999AA1...'
}
)
Error Handling
Exception Handling
import azotte
from azotte.errors import (
AzotteError,
APIError,
AuthenticationError,
ValidationError,
RateLimitError
)
try:
customer = client.customers.create(
name='', # Invalid: empty name
email='invalid-email' # Invalid: bad format
)
except ValidationError as e:
print(f"Validation failed: {e.user_message}")
for field, errors in e.field_errors.items():
print(f" {field}: {', '.join(errors)}")
except AuthenticationError as e:
print(f"Authentication failed: {e}")
# Handle invalid API key
except RateLimitError as e:
print(f"Rate limited. Retry after: {e.retry_after} seconds")
# Implement exponential backoff
except APIError as e:
print(f"API error: {e.message}")
print(f"Request ID: {e.request_id}")
print(f"Status code: {e.status_code}")
except AzotteError as e:
print(f"Azotte error: {e}")
# Handle other Azotte-specific errors
except Exception as e:
print(f"Unexpected error: {e}")
# Handle network errors, etc.
Retry Logic
import time
from azotte.errors import RateLimitError, APIError
def create_customer_with_retry(customer_data, max_retries=3):
for attempt in range(max_retries + 1):
try:
return client.customers.create(**customer_data)
except RateLimitError as e:
if attempt == max_retries:
raise
time.sleep(e.retry_after or 2 ** attempt)
except APIError as e:
if e.status_code >= 500 and attempt < max_retries:
time.sleep(2 ** attempt) # Exponential backoff
else:
raise
Testing
Mock Client
import azotte
from unittest.mock import Mock, patch
# Mock the entire client
mock_client = Mock(spec=azotte.Client)
mock_client.customers.create.return_value = Mock(
id='cus_test_123',
name='Test Customer',
email='test@example.com'
)
# Test your function
def test_create_customer():
with patch('azotte.Client', return_value=mock_client):
customer = create_customer('Test Customer', 'test@example.com')
assert customer.id == 'cus_test_123'
Test Mode
# Use test API keys for testing
test_client = azotte.Client(
api_key='sk_test_...',
environment='sandbox'
)
# Create test data
test_customer = test_client.customers.create(
name='Test Customer',
email='test@example.com'
)
# Test payments with test card numbers
test_payment = test_client.payments.create(
amount=2000,
currency='usd',
customer_id=test_customer.id,
payment_method_data={
'type': 'card',
'card': {
'number': '4242424242424242', # Test Visa card
'exp_month': 12,
'exp_year': 2025,
'cvc': '123'
}
}
)
Performance Optimization
Connection Pooling
import azotte
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
# Custom session with connection pooling
session = requests.Session()
retry_strategy = Retry(
total=3,
status_forcelist=[429, 500, 502, 503, 504],
backoff_factor=1
)
adapter = HTTPAdapter(
pool_connections=10,
pool_maxsize=20,
max_retries=retry_strategy
)
session.mount("https://", adapter)
client = azotte.Client(
api_key='sk_live_...',
session=session
)
Async Support
import asyncio
import azotte
# Async client
async_client = azotte.AsyncClient(api_key='sk_live_...')
async def create_multiple_customers(customer_data_list):
tasks = [
async_client.customers.create(**data)
for data in customer_data_list
]
customers = await asyncio.gather(*tasks)
return customers
# Usage
customer_data = [
{'name': 'Customer 1', 'email': 'c1@example.com'},
{'name': 'Customer 2', 'email': 'c2@example.com'},
]
customers = asyncio.run(create_multiple_customers(customer_data))
Best Practices
Idempotency
import uuid
# Use idempotency keys for critical operations
idempotency_key = str(uuid.uuid4())
customer = client.customers.create(
name='John Doe',
email='john@example.com',
idempotency_key=idempotency_key
)
Pagination
# Efficient pagination for large datasets
def process_all_customers():
for customer in client.customers.auto_paging_iter(limit=100):
process_customer(customer)
def process_customer(customer):
print(f"Processing {customer.name}")
# Your business logic here
Next Steps
- Learn about .NET SDK
- Understand JavaScript SDK
- Explore cURL Examples