mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
179 lines
No EOL
6.9 KiB
Python
179 lines
No EOL
6.9 KiB
Python
from typing import Literal
|
|
from fastapi import HTTPException, Request
|
|
from sqlmodel import Session, select
|
|
import stripe
|
|
import logging
|
|
from src.db.payments.payments_users import PaymentStatusEnum
|
|
from src.db.users import InternalUser
|
|
from src.services.payments.payments_users import update_payment_user_status
|
|
from src.services.payments.payments_stripe import get_stripe_internal_credentials
|
|
from src.db.payments.payments import PaymentsConfig, PaymentsConfigUpdate
|
|
from src.services.payments.payments_config import update_payments_config
|
|
from src.services.payments.utils.stripe_utils import get_org_id_from_stripe_account
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def handle_stripe_webhook(
|
|
request: Request,
|
|
webhook_type: Literal["connect", "standard"],
|
|
db_session: Session,
|
|
) -> dict:
|
|
# Get Stripe credentials
|
|
creds = await get_stripe_internal_credentials()
|
|
webhook_secret = creds.get(f'stripe_webhook_{webhook_type}_secret')
|
|
stripe.api_key = creds.get("stripe_secret_key")
|
|
|
|
if not webhook_secret:
|
|
logger.error("Stripe webhook secret not configured")
|
|
raise HTTPException(status_code=400, detail="Stripe webhook secret not configured")
|
|
|
|
# Get request data
|
|
payload = await request.body()
|
|
sig_header = request.headers.get('stripe-signature')
|
|
|
|
try:
|
|
# Verify webhook signature
|
|
event = stripe.Webhook.construct_event(payload, sig_header, webhook_secret)
|
|
except ValueError:
|
|
logger.error(ValueError)
|
|
raise HTTPException(status_code=400, detail="Invalid payload")
|
|
except stripe.SignatureVerificationError:
|
|
logger.error(stripe.SignatureVerificationError)
|
|
raise HTTPException(status_code=400, detail="Invalid signature")
|
|
|
|
try:
|
|
event_type = event.type
|
|
event_data = event.data.object
|
|
|
|
# Get organization ID based on the event type
|
|
stripe_account_id = event.account
|
|
if not stripe_account_id:
|
|
logger.error("Stripe account ID not found")
|
|
raise HTTPException(status_code=400, detail="Stripe account ID not found")
|
|
|
|
org_id = await get_org_id_from_stripe_account(stripe_account_id, db_session)
|
|
|
|
# Handle internal account events
|
|
if event_type == 'account.application.authorized':
|
|
statement = select(PaymentsConfig).where(PaymentsConfig.org_id == org_id)
|
|
config = db_session.exec(statement).first()
|
|
|
|
if not config:
|
|
logger.error("No payments configuration found for this organization")
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail="No payments configuration found for this organization"
|
|
)
|
|
|
|
config_data = config.model_dump()
|
|
config_data.update({
|
|
"enabled": True,
|
|
"active": True,
|
|
"provider_config": {
|
|
**config.provider_config,
|
|
"onboarding_completed": True
|
|
}
|
|
})
|
|
await update_payments_config(
|
|
request,
|
|
org_id,
|
|
PaymentsConfigUpdate(**config_data),
|
|
InternalUser(),
|
|
db_session,
|
|
)
|
|
|
|
logger.info(f"Account authorized for organization {org_id}")
|
|
return {"status": "success", "message": "Account authorized successfully"}
|
|
|
|
elif event_type == 'account.application.deauthorized':
|
|
statement = select(PaymentsConfig).where(PaymentsConfig.org_id == org_id)
|
|
config = db_session.exec(statement).first()
|
|
|
|
if not config:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail="No payments configuration found for this organization"
|
|
)
|
|
|
|
config_data = config.model_dump()
|
|
config_data.update({
|
|
"enabled": True,
|
|
"active": False,
|
|
"provider_config": {
|
|
**config.provider_config,
|
|
"onboarding_completed": False
|
|
}
|
|
})
|
|
await update_payments_config(
|
|
request,
|
|
org_id,
|
|
PaymentsConfigUpdate(**config_data),
|
|
InternalUser(),
|
|
db_session,
|
|
)
|
|
|
|
logger.info(f"Account deauthorized for organization {org_id}")
|
|
return {"status": "success", "message": "Account deauthorized successfully"}
|
|
|
|
# Handle payment-related events
|
|
elif event_type == "checkout.session.completed":
|
|
session = event_data
|
|
payment_user_id = int(session.get("metadata", {}).get("payment_user_id"))
|
|
|
|
if session.get("mode") == "subscription":
|
|
if session.get("subscription"):
|
|
await update_payment_user_status(
|
|
request=request,
|
|
org_id=org_id,
|
|
payment_user_id=payment_user_id,
|
|
status=PaymentStatusEnum.ACTIVE,
|
|
current_user=InternalUser(),
|
|
db_session=db_session,
|
|
)
|
|
else:
|
|
if session.get("payment_status") == "paid":
|
|
await update_payment_user_status(
|
|
request=request,
|
|
org_id=org_id,
|
|
payment_user_id=payment_user_id,
|
|
status=PaymentStatusEnum.COMPLETED,
|
|
current_user=InternalUser(),
|
|
db_session=db_session,
|
|
)
|
|
|
|
elif event_type == "customer.subscription.deleted":
|
|
subscription = event_data
|
|
payment_user_id = int(subscription.get("metadata", {}).get("payment_user_id"))
|
|
|
|
await update_payment_user_status(
|
|
request=request,
|
|
org_id=org_id,
|
|
payment_user_id=payment_user_id,
|
|
status=PaymentStatusEnum.CANCELLED,
|
|
current_user=InternalUser(),
|
|
db_session=db_session,
|
|
)
|
|
|
|
elif event_type == "payment_intent.payment_failed":
|
|
payment_intent = event_data
|
|
payment_user_id = int(payment_intent.get("metadata", {}).get("payment_user_id"))
|
|
|
|
await update_payment_user_status(
|
|
request=request,
|
|
org_id=org_id,
|
|
payment_user_id=payment_user_id,
|
|
status=PaymentStatusEnum.FAILED,
|
|
current_user=InternalUser(),
|
|
db_session=db_session,
|
|
)
|
|
|
|
else:
|
|
logger.warning(f"Unhandled event type: {event_type}")
|
|
return {"status": "ignored", "message": f"Unhandled event type: {event_type}"}
|
|
|
|
return {"status": "success"}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing webhook: {str(e)}")
|
|
raise HTTPException(status_code=400, detail=f"Error processing webhook: {str(e)}") |