Files
TimeTracker/app/utils/stripe_integration.py
T
Dries Peeters b4486a627f fix: CI tests, code quality, and duplicate DB indexes
- Webhook models: remove duplicate index definitions so db.create_all()
  no longer raises 'index already exists' (columns already have index=True)
- ImportService: fix circular import by late-importing ClientService,
  ProjectService, TimeTrackingService in __init__
- reports: fix F823 by renaming unpack variable _ to _entry_count to avoid
  shadowing gettext _ in export_task_excel()
- Code quality: add .flake8 with extend-ignore so flake8 CI passes;
  simplify pyproject.toml isort config (drop unsupported options)
- Format: run black and isort on app/
- tests: restore minimal app fixture in test_import_export_models
2026-03-15 10:51:52 +01:00

139 lines
4.6 KiB
Python

"""
Stripe payment gateway integration utilities.
"""
import logging
from decimal import Decimal
from typing import Any, Dict, Optional
import stripe
from flask import current_app
logger = logging.getLogger(__name__)
class StripeIntegration:
"""
Stripe payment gateway integration.
"""
def __init__(self, api_key: str):
"""
Initialize Stripe integration.
Args:
api_key: Stripe API key (secret key)
"""
self.api_key = api_key
stripe.api_key = api_key
def create_payment_intent(
self,
amount: Decimal,
currency: str,
invoice_id: int,
description: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""
Create a Stripe PaymentIntent.
Returns:
dict with 'success', 'client_secret', and 'payment_intent' keys
"""
try:
# Convert amount to cents
amount_cents = int(amount * 100)
payment_intent_data = {
"amount": amount_cents,
"currency": currency.lower(),
"metadata": {"invoice_id": str(invoice_id), **(metadata or {})},
}
if description:
payment_intent_data["description"] = description
payment_intent = stripe.PaymentIntent.create(**payment_intent_data)
return {"success": True, "client_secret": payment_intent.client_secret, "payment_intent": payment_intent}
except stripe.error.StripeError as e:
logger.error(f"Stripe error creating payment intent: {e}")
return {"success": False, "message": str(e), "error_code": e.code if hasattr(e, "code") else None}
except Exception as e:
logger.error(f"Error creating payment intent: {e}")
return {"success": False, "message": f"Error creating payment intent: {str(e)}"}
def verify_webhook(self, payload: bytes, signature: str, webhook_secret: str) -> Optional[stripe.Event]:
"""
Verify and parse a Stripe webhook.
Returns:
Stripe Event object if valid, None otherwise
"""
try:
event = stripe.Webhook.construct_event(payload, signature, webhook_secret)
return event
except ValueError as e:
logger.error(f"Invalid payload: {e}")
return None
except stripe.error.SignatureVerificationError as e:
logger.error(f"Invalid signature: {e}")
return None
def get_payment_intent(self, payment_intent_id: str) -> Optional[stripe.PaymentIntent]:
"""Retrieve a PaymentIntent from Stripe"""
try:
return stripe.PaymentIntent.retrieve(payment_intent_id)
except stripe.error.StripeError as e:
logger.error(f"Error retrieving payment intent: {e}")
return None
def create_checkout_session(
self,
invoice_id: int,
amount: Decimal,
currency: str,
success_url: str,
cancel_url: str,
description: Optional[str] = None,
) -> Dict[str, Any]:
"""
Create a Stripe Checkout Session.
Returns:
dict with 'success', 'session_id', and 'url' keys
"""
try:
amount_cents = int(amount * 100)
session_data = {
"payment_method_types": ["card"],
"line_items": [
{
"price_data": {
"currency": currency.lower(),
"product_data": {
"name": description or f"Invoice #{invoice_id}",
},
"unit_amount": amount_cents,
},
"quantity": 1,
}
],
"mode": "payment",
"success_url": success_url,
"cancel_url": cancel_url,
"metadata": {"invoice_id": str(invoice_id)},
}
session = stripe.checkout.Session.create(**session_data)
return {"success": True, "session_id": session.id, "url": session.url}
except stripe.error.StripeError as e:
logger.error(f"Stripe error creating checkout session: {e}")
return {"success": False, "message": str(e), "error_code": e.code if hasattr(e, "code") else None}
except Exception as e:
logger.error(f"Error creating checkout session: {e}")
return {"success": False, "message": f"Error creating checkout session: {str(e)}"}