feat: stripe improvments

This commit is contained in:
deepvasoya 2025-05-28 15:28:36 +05:30
parent 6c4fcca9bd
commit e6fbae493b
7 changed files with 125 additions and 38 deletions

View File

@ -1,27 +1,35 @@
from fastapi import APIRouter, Request
from fastapi import APIRouter, Depends, Request
from services.stripeServices import StripeServices
from middleware.auth_dependency import auth_required
from schemas.ApiResponse import ApiResponse
router = APIRouter()
stripe_service = StripeServices()
@router.post("/create-checkout-session")
async def create_checkout_session(user_id: int):
return await stripe_service.create_checkout_session(1)
# @router.post("/create-checkout-session")
# async def create_checkout_session(user_id: int):
# return await stripe_service.create_checkout_session(1)
@router.post("/create-subscription-checkout")
async def create_subscription_checkout():
return await stripe_service.create_subscription_checkout(
fees_to_be={
"per_call_charges": 10,
"setup_fees": 100,
"subscription_fees": 100,
"total": 210
},
clinic_id=1,
account_id="acct_1RT1UFPTNqn2kWQ8",
customer_id="cus_SNn49FDltUcSLP"
)
# @router.post("/create-subscription-checkout")
# async def create_subscription_checkout():
# return await stripe_service.create_subscription_checkout(
# fees_to_be={
# "per_call_charges": 10,
# "setup_fees": 100,
# "subscription_fees": 100,
# "total": 210
# },
# clinic_id=1,
# account_id="acct_1RT1UFPTNqn2kWQ8",
# customer_id="cus_SNn49FDltUcSLP"
# )
@router.post("/create-payment-session", dependencies=[Depends(auth_required)])
async def create_payment_session(req:Request):
session = await stripe_service.create_payment_session(req.state.user)
return ApiResponse(data=session, message="Payment session created successfully")
@router.post("/webhook")
async def stripe_webhook(request: Request):

View File

@ -1,6 +1,6 @@
from database import Base
from models.CustomBase import CustomBase
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy import Column, Integer, String, ForeignKey, Numeric
from sqlalchemy.orm import relationship
class PaymentLogs(Base, CustomBase):
@ -8,7 +8,7 @@ class PaymentLogs(Base, CustomBase):
id = Column(Integer, primary_key=True, index=True)
customer_id = Column(String)
account_id = Column(String)
amount = Column(Integer)
amount = Column(Numeric(10, 2))
clinic_id = Column(Integer)
unique_clinic_id = Column(String)
payment_status = Column(String)

12
models/PaymentSessions.py Normal file
View File

@ -0,0 +1,12 @@
from sqlalchemy import Column, Integer, String
from database import Base
from .CustomBase import CustomBase
class PaymentSessions(Base, CustomBase):
__tablename__ = "payment_sessions"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String(255), unique=True, index=True)
customer_id = Column(String, nullable=False)
clinic_id = Column(Integer, nullable=False)
status = Column(String, nullable=False)

View File

@ -18,6 +18,7 @@ from .ResetPasswordTokens import ResetPasswordTokens
from .ClinicOffers import ClinicOffers
from .StripeUsers import StripeUsers
from .PaymentLogs import PaymentLogs
from .PaymentSessions import PaymentSessions
__all__ = [
"Users",
@ -39,5 +40,6 @@ __all__ = [
"ResetPasswordTokens",
"ClinicOffers",
"StripeUsers",
"PaymentLogs"
"PaymentLogs",
"PaymentSessions"
]

View File

@ -200,7 +200,8 @@ class ClinicServices:
Clinics.status.in_([
ClinicStatus.ACTIVE,
ClinicStatus.UNDER_REVIEW,
ClinicStatus.REJECTED
ClinicStatus.REJECTED,
ClinicStatus.PAYMENT_DUE
])
).group_by(Clinics.status).all()
@ -209,13 +210,15 @@ class ClinicServices:
"totalClinics": totalClinics,
"totalActiveClinics": 0,
"totalUnderReviewClinics": 0,
"totalRejectedClinics": 0
"totalRejectedClinics": 0,
"totalPaymentDueClinics": 0
}
# Map status values to their respective count keys
status_to_key = {
ClinicStatus.ACTIVE: "totalActiveClinics",
ClinicStatus.UNDER_REVIEW: "totalUnderReviewClinics",
ClinicStatus.PAYMENT_DUE: "totalPaymentDueClinics",
ClinicStatus.REJECTED: "totalRejectedClinics"
}

View File

@ -2,19 +2,27 @@ import json
import os
import dotenv
dotenv.load_dotenv()
from models import Clinics
from models import ClinicOffers, StripeUsers
from services.dashboardService import DashboardService
from exceptions.validation_exception import ValidationException
from exceptions.resource_not_found_exception import ResourceNotFoundException
from exceptions.unauthorized_exception import UnauthorizedException
from models import Clinics,PaymentSessions
import uuid
from fastapi import Request
from datetime import datetime
from models import PaymentLogs
from enums.enums import ClinicStatus
from enums.enums import ClinicStatus, UserType
from database import get_db
from sqlalchemy.orm import Session
import stripe
from loguru import logger
from decimal import Decimal
class StripeServices:
@ -22,6 +30,7 @@ class StripeServices:
self.db: Session = next(get_db())
self.logger = logger
self.webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET")
self.dashboard_service = DashboardService()
async def create_customer(self, user_id: int, email: str, name: str):
try:
@ -65,6 +74,50 @@ class StripeServices:
self.logger.error(f"Error deleting account: {e}")
raise
async def create_payment_session(self, user):
try:
if user["userType"] != UserType.CLINIC_ADMIN:
raise UnauthorizedException("User is not authorized to perform this action")
clinic = user["created_clinics"][0]
if clinic["status"] != ClinicStatus.PAYMENT_DUE:
raise ValidationException("Clinic is not due for payment")
customer = self.db.query(StripeUsers).filter(StripeUsers.user_id == user['id']).first()
if not customer:
raise ResourceNotFoundException("Customer not found")
clinic_offers = self.db.query(ClinicOffers).filter(ClinicOffers.clinic_email == clinic["email"]).first()
signup_pricing= await self.dashboard_service.get_signup_pricing_master()
fees_to_be = {
"setup_fees": signup_pricing.setup_fees,
"subscription_fees": signup_pricing.subscription_fees,
"per_call_charges": signup_pricing.per_call_charges,
"total": signup_pricing.setup_fees + signup_pricing.subscription_fees + signup_pricing.per_call_charges
}
if clinic_offers:
fees_to_be["setup_fees"] = clinic_offers.setup_fees
fees_to_be["per_call_charges"] = clinic_offers.per_call_charges
fees_to_be["total"] = clinic_offers.setup_fees + fees_to_be["subscription_fees"] + clinic_offers.per_call_charges
# remove previouis payment session
self.db.query(PaymentSessions).filter(PaymentSessions.clinic_id == clinic["id"]).delete()
payment_link = await self.create_subscription_checkout(fees_to_be, clinic["id"], customer.account_id, customer.customer_id)
return payment_link.url
except Exception as e:
self.logger.error(f"Error creating payment session: {e}")
raise
finally:
self.db.close()
async def create_checkout_session(self, user_id: int):
try:
checkout_session = stripe.checkout.Session.create(
@ -118,7 +171,7 @@ class StripeServices:
'product_data': {
'name': 'Monthly Subscription',
},
'unit_amount': fees_to_be["subscription_fees"],
'unit_amount': int(fees_to_be["subscription_fees"] * 100), # Convert to cents
'recurring': {
'interval': 'year',
},
@ -132,7 +185,7 @@ class StripeServices:
'product_data': {
'name': 'Per Call',
},
'unit_amount': fees_to_be["per_call_charges"],
'unit_amount': int(fees_to_be["per_call_charges"] * 100), # Convert to cents
},
'quantity': 1,
})
@ -143,7 +196,7 @@ class StripeServices:
'product_data': {
'name': 'Setup Fee',
},
'unit_amount': fees_to_be["setup_fees"],
'unit_amount': int(fees_to_be["setup_fees"] * 100), # Convert to cents
},
'quantity': 1,
})
@ -152,7 +205,8 @@ class StripeServices:
"clinic_id": clinic_id,
"unique_clinic_id": unique_clinic_id,
"account_id": account_id,
"customer_id": customer_id
"customer_id": customer_id,
"fees_to_be": json.dumps(fees_to_be)
}
session_data = {
@ -173,13 +227,22 @@ class StripeServices:
payment_log = PaymentLogs(
customer_id=customer_id,
account_id=account_id,
amount=fees_to_be["total"],
amount=Decimal(str(fees_to_be["total"])), # Keep as Decimal for database storage
clinic_id=clinic_id,
unique_clinic_id=unique_clinic_id,
payment_status="pending",
metadata_logs=json.dumps(metadata)
)
new_payment_session = PaymentSessions(
session_id=session.id,
customer_id=customer_id,
clinic_id=clinic_id,
status="pending"
)
self.db.add(payment_log)
self.db.add(new_payment_session)
self.db.commit()
return session
@ -201,25 +264,26 @@ class StripeServices:
if event["type"] == "invoice.payment_succeeded":
pass
# if event["type"] == "payment_intent.succeeded":
# await self.update_payment_log(event["data"]["object"]["metadata"]["unique_clinic_id"])
if event["type"] == "checkout.session.async_payment_succeeded":
self.logger.info("Async payment succeeded")
elif event["type"] == "checkout.session.completed":
await self.update_payment_log(event["data"]["object"]["metadata"]["unique_clinic_id"], event["data"]["object"]["metadata"]["clinic_id"])
self.update_payment_log(event["data"]["object"]["metadata"]["unique_clinic_id"], event["data"]["object"]["metadata"]["clinic_id"])
# TODO: handle subscription period end
return event
except ValueError as e:
self.logger.error(f"Invalid payload: {e}")
raise
except stripe.error.SignatureVerificationError as e:
self.logger.error(f"Invalid signature: {e}")
raise
finally:
self.db.close()
async def update_payment_log(self, unique_clinic_id:str, clinic_id:int):
def update_payment_log(self, unique_clinic_id:str, clinic_id:int):
try:
payment_log = self.db.query(PaymentLogs).filter(PaymentLogs.unique_clinic_id == unique_clinic_id).first()
self.db.query(PaymentSessions).filter(PaymentSessions.clinic_id == clinic_id).delete()
if payment_log:
payment_log.payment_status = "success"
self.db.commit()
@ -231,6 +295,5 @@ class StripeServices:
except Exception as e:
self.logger.error(f"Error updating payment log: {e}")
raise
finally:
self.db.close()

View File

@ -137,7 +137,6 @@ class UserServices:
# Send mail to admin in a non-blocking way using background tasks
if background_tasks:
background_tasks.add_task(self._send_emails_to_admins, clinic.email)
# If no background_tasks provided, we don't send emails
offer = await self.clinic_service.get_clinic_offer_by_clinic_email(clinic.email)
@ -155,7 +154,7 @@ class UserServices:
fees_to_be["per_call_charges"] = offer.per_call_charges
fees_to_be["total"] = offer.setup_fees + fees_to_be["subscription_fees"] + offer.per_call_charges
payment_link = await self.stripe_service.create_subscription_checkout(fees_to_be, new_clinic.id, stripe_account.id,stripe_customer.id)
payment_link = await self.stripe_service.create_subscription_checkout(fees_to_be, 1, stripe_account.id,stripe_customer.id)
return payment_link.url
except Exception as e: