feat: stripe improvments
This commit is contained in:
parent
6c4fcca9bd
commit
e6fbae493b
|
|
@ -1,27 +1,35 @@
|
||||||
from fastapi import APIRouter, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
from services.stripeServices import StripeServices
|
from services.stripeServices import StripeServices
|
||||||
|
from middleware.auth_dependency import auth_required
|
||||||
|
from schemas.ApiResponse import ApiResponse
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
stripe_service = StripeServices()
|
stripe_service = StripeServices()
|
||||||
|
|
||||||
@router.post("/create-checkout-session")
|
# @router.post("/create-checkout-session")
|
||||||
async def create_checkout_session(user_id: int):
|
# async def create_checkout_session(user_id: int):
|
||||||
return await stripe_service.create_checkout_session(1)
|
# return await stripe_service.create_checkout_session(1)
|
||||||
|
|
||||||
@router.post("/create-subscription-checkout")
|
# @router.post("/create-subscription-checkout")
|
||||||
async def create_subscription_checkout():
|
# async def create_subscription_checkout():
|
||||||
return await stripe_service.create_subscription_checkout(
|
# return await stripe_service.create_subscription_checkout(
|
||||||
fees_to_be={
|
# fees_to_be={
|
||||||
"per_call_charges": 10,
|
# "per_call_charges": 10,
|
||||||
"setup_fees": 100,
|
# "setup_fees": 100,
|
||||||
"subscription_fees": 100,
|
# "subscription_fees": 100,
|
||||||
"total": 210
|
# "total": 210
|
||||||
},
|
# },
|
||||||
clinic_id=1,
|
# clinic_id=1,
|
||||||
account_id="acct_1RT1UFPTNqn2kWQ8",
|
# account_id="acct_1RT1UFPTNqn2kWQ8",
|
||||||
customer_id="cus_SNn49FDltUcSLP"
|
# 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")
|
@router.post("/webhook")
|
||||||
async def stripe_webhook(request: Request):
|
async def stripe_webhook(request: Request):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from database import Base
|
from database import Base
|
||||||
from models.CustomBase import CustomBase
|
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
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
class PaymentLogs(Base, CustomBase):
|
class PaymentLogs(Base, CustomBase):
|
||||||
|
|
@ -8,7 +8,7 @@ class PaymentLogs(Base, CustomBase):
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
customer_id = Column(String)
|
customer_id = Column(String)
|
||||||
account_id = Column(String)
|
account_id = Column(String)
|
||||||
amount = Column(Integer)
|
amount = Column(Numeric(10, 2))
|
||||||
clinic_id = Column(Integer)
|
clinic_id = Column(Integer)
|
||||||
unique_clinic_id = Column(String)
|
unique_clinic_id = Column(String)
|
||||||
payment_status = Column(String)
|
payment_status = Column(String)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
@ -18,6 +18,7 @@ from .ResetPasswordTokens import ResetPasswordTokens
|
||||||
from .ClinicOffers import ClinicOffers
|
from .ClinicOffers import ClinicOffers
|
||||||
from .StripeUsers import StripeUsers
|
from .StripeUsers import StripeUsers
|
||||||
from .PaymentLogs import PaymentLogs
|
from .PaymentLogs import PaymentLogs
|
||||||
|
from .PaymentSessions import PaymentSessions
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Users",
|
"Users",
|
||||||
|
|
@ -39,5 +40,6 @@ __all__ = [
|
||||||
"ResetPasswordTokens",
|
"ResetPasswordTokens",
|
||||||
"ClinicOffers",
|
"ClinicOffers",
|
||||||
"StripeUsers",
|
"StripeUsers",
|
||||||
"PaymentLogs"
|
"PaymentLogs",
|
||||||
|
"PaymentSessions"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,8 @@ class ClinicServices:
|
||||||
Clinics.status.in_([
|
Clinics.status.in_([
|
||||||
ClinicStatus.ACTIVE,
|
ClinicStatus.ACTIVE,
|
||||||
ClinicStatus.UNDER_REVIEW,
|
ClinicStatus.UNDER_REVIEW,
|
||||||
ClinicStatus.REJECTED
|
ClinicStatus.REJECTED,
|
||||||
|
ClinicStatus.PAYMENT_DUE
|
||||||
])
|
])
|
||||||
).group_by(Clinics.status).all()
|
).group_by(Clinics.status).all()
|
||||||
|
|
||||||
|
|
@ -209,13 +210,15 @@ class ClinicServices:
|
||||||
"totalClinics": totalClinics,
|
"totalClinics": totalClinics,
|
||||||
"totalActiveClinics": 0,
|
"totalActiveClinics": 0,
|
||||||
"totalUnderReviewClinics": 0,
|
"totalUnderReviewClinics": 0,
|
||||||
"totalRejectedClinics": 0
|
"totalRejectedClinics": 0,
|
||||||
|
"totalPaymentDueClinics": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Map status values to their respective count keys
|
# Map status values to their respective count keys
|
||||||
status_to_key = {
|
status_to_key = {
|
||||||
ClinicStatus.ACTIVE: "totalActiveClinics",
|
ClinicStatus.ACTIVE: "totalActiveClinics",
|
||||||
ClinicStatus.UNDER_REVIEW: "totalUnderReviewClinics",
|
ClinicStatus.UNDER_REVIEW: "totalUnderReviewClinics",
|
||||||
|
ClinicStatus.PAYMENT_DUE: "totalPaymentDueClinics",
|
||||||
ClinicStatus.REJECTED: "totalRejectedClinics"
|
ClinicStatus.REJECTED: "totalRejectedClinics"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,27 @@ import json
|
||||||
import os
|
import os
|
||||||
import dotenv
|
import dotenv
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dotenv.load_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
|
import uuid
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from models import PaymentLogs
|
from models import PaymentLogs
|
||||||
from enums.enums import ClinicStatus
|
from enums.enums import ClinicStatus, UserType
|
||||||
|
|
||||||
from database import get_db
|
from database import get_db
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
import stripe
|
import stripe
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
class StripeServices:
|
class StripeServices:
|
||||||
|
|
@ -22,6 +30,7 @@ class StripeServices:
|
||||||
self.db: Session = next(get_db())
|
self.db: Session = next(get_db())
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET")
|
self.webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET")
|
||||||
|
self.dashboard_service = DashboardService()
|
||||||
|
|
||||||
async def create_customer(self, user_id: int, email: str, name: str):
|
async def create_customer(self, user_id: int, email: str, name: str):
|
||||||
try:
|
try:
|
||||||
|
|
@ -65,6 +74,50 @@ class StripeServices:
|
||||||
self.logger.error(f"Error deleting account: {e}")
|
self.logger.error(f"Error deleting account: {e}")
|
||||||
raise
|
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):
|
async def create_checkout_session(self, user_id: int):
|
||||||
try:
|
try:
|
||||||
checkout_session = stripe.checkout.Session.create(
|
checkout_session = stripe.checkout.Session.create(
|
||||||
|
|
@ -118,7 +171,7 @@ class StripeServices:
|
||||||
'product_data': {
|
'product_data': {
|
||||||
'name': 'Monthly Subscription',
|
'name': 'Monthly Subscription',
|
||||||
},
|
},
|
||||||
'unit_amount': fees_to_be["subscription_fees"],
|
'unit_amount': int(fees_to_be["subscription_fees"] * 100), # Convert to cents
|
||||||
'recurring': {
|
'recurring': {
|
||||||
'interval': 'year',
|
'interval': 'year',
|
||||||
},
|
},
|
||||||
|
|
@ -132,7 +185,7 @@ class StripeServices:
|
||||||
'product_data': {
|
'product_data': {
|
||||||
'name': 'Per Call',
|
'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,
|
'quantity': 1,
|
||||||
})
|
})
|
||||||
|
|
@ -143,7 +196,7 @@ class StripeServices:
|
||||||
'product_data': {
|
'product_data': {
|
||||||
'name': 'Setup Fee',
|
'name': 'Setup Fee',
|
||||||
},
|
},
|
||||||
'unit_amount': fees_to_be["setup_fees"],
|
'unit_amount': int(fees_to_be["setup_fees"] * 100), # Convert to cents
|
||||||
},
|
},
|
||||||
'quantity': 1,
|
'quantity': 1,
|
||||||
})
|
})
|
||||||
|
|
@ -152,7 +205,8 @@ class StripeServices:
|
||||||
"clinic_id": clinic_id,
|
"clinic_id": clinic_id,
|
||||||
"unique_clinic_id": unique_clinic_id,
|
"unique_clinic_id": unique_clinic_id,
|
||||||
"account_id": account_id,
|
"account_id": account_id,
|
||||||
"customer_id": customer_id
|
"customer_id": customer_id,
|
||||||
|
"fees_to_be": json.dumps(fees_to_be)
|
||||||
}
|
}
|
||||||
|
|
||||||
session_data = {
|
session_data = {
|
||||||
|
|
@ -173,13 +227,22 @@ class StripeServices:
|
||||||
payment_log = PaymentLogs(
|
payment_log = PaymentLogs(
|
||||||
customer_id=customer_id,
|
customer_id=customer_id,
|
||||||
account_id=account_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,
|
clinic_id=clinic_id,
|
||||||
unique_clinic_id=unique_clinic_id,
|
unique_clinic_id=unique_clinic_id,
|
||||||
payment_status="pending",
|
payment_status="pending",
|
||||||
metadata_logs=json.dumps(metadata)
|
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(payment_log)
|
||||||
|
self.db.add(new_payment_session)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
return session
|
return session
|
||||||
|
|
@ -201,25 +264,26 @@ class StripeServices:
|
||||||
if event["type"] == "invoice.payment_succeeded":
|
if event["type"] == "invoice.payment_succeeded":
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# if event["type"] == "payment_intent.succeeded":
|
if event["type"] == "checkout.session.async_payment_succeeded":
|
||||||
# await self.update_payment_log(event["data"]["object"]["metadata"]["unique_clinic_id"])
|
self.logger.info("Async payment succeeded")
|
||||||
|
|
||||||
elif event["type"] == "checkout.session.completed":
|
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
|
return event
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.logger.error(f"Invalid payload: {e}")
|
self.logger.error(f"Invalid payload: {e}")
|
||||||
raise
|
|
||||||
except stripe.error.SignatureVerificationError as e:
|
except stripe.error.SignatureVerificationError as e:
|
||||||
self.logger.error(f"Invalid signature: {e}")
|
self.logger.error(f"Invalid signature: {e}")
|
||||||
raise
|
|
||||||
finally:
|
finally:
|
||||||
self.db.close()
|
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:
|
try:
|
||||||
payment_log = self.db.query(PaymentLogs).filter(PaymentLogs.unique_clinic_id == unique_clinic_id).first()
|
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:
|
if payment_log:
|
||||||
payment_log.payment_status = "success"
|
payment_log.payment_status = "success"
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
@ -231,6 +295,5 @@ class StripeServices:
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error updating payment log: {e}")
|
self.logger.error(f"Error updating payment log: {e}")
|
||||||
raise
|
|
||||||
finally:
|
finally:
|
||||||
self.db.close()
|
self.db.close()
|
||||||
|
|
@ -137,7 +137,6 @@ class UserServices:
|
||||||
# Send mail to admin in a non-blocking way using background tasks
|
# Send mail to admin in a non-blocking way using background tasks
|
||||||
if background_tasks:
|
if background_tasks:
|
||||||
background_tasks.add_task(self._send_emails_to_admins, clinic.email)
|
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)
|
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["per_call_charges"] = offer.per_call_charges
|
||||||
fees_to_be["total"] = offer.setup_fees + fees_to_be["subscription_fees"] + 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
|
return payment_link.url
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue