techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 4)
4b9d59b verified
Raw
History Blame Contribute Delete
15.5 kB
"""
Stripe Integration Adapter
Provides OAuth-based integration with Stripe for payment and billing operations.
"""
import logging
import os
import httpx
from typing import Dict, Any, List, Optional
from datetime import datetime, timedelta
from urllib.parse import urlencode
logger = logging.getLogger(__name__)
class StripeAdapter:
"""
Adapter for Stripe OAuth integration.
Supports:
- OAuth 2.0 authentication
- Payment and charge management
- Customer and subscription operations
- Invoice and billing access
"""
def __init__(self, db, workspace_id: str):
self.db = db
self.workspace_id = workspace_id
self.service_name = "stripe"
self.base_url = "https://api.stripe.com/v1"
# OAuth credentials from environment
self.client_id = os.getenv("STRIPE_CLIENT_ID")
self.client_secret = os.getenv("STRIPE_SECRET_KEY")
self.redirect_uri = os.getenv("STRIPE_REDIRECT_URI")
# Token storage
self._access_token: Optional[str] = None
_refresh_token: Optional[str] = None
_stripe_user_id: Optional[str] = None
self._token_expires_at: Optional[datetime] = None
async def get_oauth_url(self) -> str:
"""
Generate Stripe OAuth authorization URL.
Returns:
Authorization URL to redirect user to Stripe OAuth consent screen
"""
if not self.client_id:
raise ValueError("STRIPE_CLIENT_ID not configured")
# Stripe OAuth endpoint
auth_url = "https://connect.stripe.com/oauth/authorize"
# Build authorization URL
params = {
"client_id": self.client_id,
"redirect_uri": self.redirect_uri,
"response_type": "code",
"scope": "read_write",
"state": self.workspace_id, # Use workspace_id as state
}
auth_url_with_params = f"{auth_url}?{urlencode(params)}"
logger.info(f"Generated Stripe OAuth URL for workspace {self.workspace_id}")
return auth_url_with_params
async def exchange_code_for_token(self, code: str) -> Dict[str, Any]:
"""
Exchange OAuth authorization code for access token.
Args:
code: Authorization code from OAuth callback
Returns:
Token response with access_token, refresh_token, stripe_user_id, etc.
"""
if not self.client_id or not self.client_secret:
raise ValueError("Stripe OAuth credentials not configured")
token_url = "https://connect.stripe.com/oauth/token"
data = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": self.redirect_uri,
"client_id": self.client_id,
"client_secret": self.client_secret,
}
try:
async with httpx.AsyncClient() as client:
response = await client.post(token_url, data=data)
response.raise_for_status()
token_data = response.json()
# Store tokens
self._access_token = token_data.get("access_token")
_refresh_token = token_data.get("refresh_token")
_stripe_user_id = token_data.get("stripe_user_id")
logger.info(f"Successfully obtained Stripe access token for workspace {self.workspace_id}")
return token_data
except httpx.HTTPStatusError as e:
logger.error(f"Stripe token exchange failed: {e}")
raise
async def test_connection(self) -> bool:
"""
Test the Stripe API connection.
Returns:
True if connection successful, False otherwise
"""
if not self._access_token:
return False
try:
async with httpx.AsyncClient() as client:
# Test by getting account info
response = await client.get(
f"{self.base_url}/account",
headers={
"Authorization": f"Bearer {self._access_token}"
}
)
response.raise_for_status()
logger.info(f"Stripe connection test successful for workspace {self.workspace_id}")
return True
except Exception as e:
logger.error(f"Stripe connection test failed: {e}")
return False
async def get_customers(self, limit: int = 20, starting_after: str = None) -> List[Dict[str, Any]]:
"""
Retrieve Stripe customers.
Args:
limit: Maximum number of results
starting_after: Cursor for pagination
Returns:
List of customer objects
"""
if not self._access_token:
raise ValueError("Stripe access token not available")
try:
params = {"limit": limit}
if starting_after:
params["starting_after"] = starting_after
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/customers",
headers={
"Authorization": f"Bearer {self._access_token}"
},
params=params
)
response.raise_for_status()
data = response.json()
customers = data.get("data", [])
logger.info(f"Retrieved {len(customers)} Stripe customers for workspace {self.workspace_id}")
return customers
except Exception as e:
logger.error(f"Failed to retrieve Stripe customers: {e}")
raise
async def get_customer(self, customer_id: str) -> Dict[str, Any]:
"""
Retrieve a specific Stripe customer by ID.
Args:
customer_id: Stripe customer ID
Returns:
Customer details
"""
if not self._access_token:
raise ValueError("Stripe access token not available")
try:
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/customers/{customer_id}",
headers={
"Authorization": f"Bearer {self._access_token}"
}
)
response.raise_for_status()
customer = response.json()
logger.info(f"Retrieved Stripe customer {customer_id} for workspace {self.workspace_id}")
return customer
except Exception as e:
logger.error(f"Failed to retrieve Stripe customer {customer_id}: {e}")
raise
async def create_customer(self, name: str = None, email: str = None,
description: str = None, **metadata) -> Dict[str, Any]:
"""
Create a new Stripe customer.
Args:
name: Customer name
email: Customer email
description: Customer description
**metadata: Additional metadata
Returns:
Created customer object
"""
if not self._access_token:
raise ValueError("Stripe access token not available")
try:
customer_data = {}
if name:
customer_data["name"] = name
if email:
customer_data["email"] = email
if description:
customer_data["description"] = description
if metadata:
customer_data["metadata"] = metadata
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/customers",
headers={
"Authorization": f"Bearer {self._access_token}"
},
json=customer_data
)
response.raise_for_status()
customer = response.json()
logger.info(f"Created Stripe customer {customer.get('id')} for workspace {self.workspace_id}")
return customer
except Exception as e:
logger.error(f"Failed to create Stripe customer: {e}")
raise
async def get_charges(self, limit: int = 20, created: Dict[str, int] = None) -> List[Dict[str, Any]]:
"""
Retrieve Stripe charges.
Args:
limit: Maximum number of results
created: Date range filter {"gte": timestamp, "lte": timestamp}
Returns:
List of charge objects
"""
if not self._access_token:
raise ValueError("Stripe access token not available")
try:
params = {"limit": limit}
if created:
for key, value in created.items():
params[f"created[{key}]"] = value
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/charges",
headers={
"Authorization": f"Bearer {self._access_token}"
},
params=params
)
response.raise_for_status()
data = response.json()
charges = data.get("data", [])
logger.info(f"Retrieved {len(charges)} Stripe charges for workspace {self.workspace_id}")
return charges
except Exception as e:
logger.error(f"Failed to retrieve Stripe charges: {e}")
raise
async def create_charge(self, amount: int, currency: str, customer: str = None,
description: str = None, **metadata) -> Dict[str, Any]:
"""
Create a new Stripe charge.
Args:
amount: Amount in cents/pennies
currency: Currency code (e.g., "usd")
customer: Customer ID
description: Charge description
**metadata: Additional metadata
Returns:
Created charge object
"""
if not self._access_token:
raise ValueError("Stripe access token not available")
try:
charge_data = {
"amount": amount,
"currency": currency,
}
if customer:
charge_data["customer"] = customer
if description:
charge_data["description"] = description
if metadata:
charge_data["metadata"] = metadata
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/charges",
headers={
"Authorization": f"Bearer {self._access_token}"
},
json=charge_data
)
response.raise_for_status()
charge = response.json()
logger.info(f"Created Stripe charge {charge.get('id')} for workspace {self.workspace_id}")
return charge
except Exception as e:
logger.error(f"Failed to create Stripe charge: {e}")
raise
async def get_invoices(self, limit: int = 20, customer: str = None) -> List[Dict[str, Any]]:
"""
Retrieve Stripe invoices.
Args:
limit: Maximum number of results
customer: Filter by customer ID
Returns:
List of invoice objects
"""
if not self._access_token:
raise ValueError("Stripe access token not available")
try:
params = {"limit": limit}
if customer:
params["customer"] = customer
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/invoices",
headers={
"Authorization": f"Bearer {self._access_token}"
},
params=params
)
response.raise_for_status()
data = response.json()
invoices = data.get("data", [])
logger.info(f"Retrieved {len(invoices)} Stripe invoices for workspace {self.workspace_id}")
return invoices
except Exception as e:
logger.error(f"Failed to retrieve Stripe invoices: {e}")
raise
async def get_subscriptions(self, limit: int = 20, customer: str = None) -> List[Dict[str, Any]]:
"""
Retrieve Stripe subscriptions.
Args:
limit: Maximum number of results
customer: Filter by customer ID
Returns:
List of subscription objects
"""
if not self._access_token:
raise ValueError("Stripe access token not available")
try:
params = {"limit": limit}
if customer:
params["customer"] = customer
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/subscriptions",
headers={
"Authorization": f"Bearer {self._access_token}"
},
params=params
)
response.raise_for_status()
data = response.json()
subscriptions = data.get("data", [])
logger.info(f"Retrieved {len(subscriptions)} Stripe subscriptions for workspace {self.workspace_id}")
return subscriptions
except Exception as e:
logger.error(f"Failed to retrieve Stripe subscriptions: {e}")
raise
async def create_payment_intent(self, amount: int, currency: str, customer: str = None,
metadata: Dict[str, Any] = None) -> Dict[str, Any]:
"""
Create a payment intent for Stripe payments.
Args:
amount: Amount in cents/pennies
currency: Currency code (e.g., "usd")
customer: Customer ID
metadata: Additional metadata
Returns:
Created payment intent object
"""
if not self._access_token:
raise ValueError("Stripe access token not available")
try:
intent_data = {
"amount": amount,
"currency": currency,
}
if customer:
intent_data["customer"] = customer
if metadata:
intent_data["metadata"] = metadata
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/payment_intents",
headers={
"Authorization": f"Bearer {self._access_token}"
},
json=intent_data
)
response.raise_for_status()
intent = response.json()
logger.info(f"Created Stripe payment intent {intent.get('id')} for workspace {self.workspace_id}")
return intent
except Exception as e:
logger.error(f"Failed to create Stripe payment intent: {e}")
raise