feat: stripe integration
This commit is contained in:
parent
611de1bf2a
commit
b198b7678e
|
|
@ -2,12 +2,10 @@ from fastapi import APIRouter, Depends, Security
|
||||||
from middleware.auth_dependency import auth_required
|
from middleware.auth_dependency import auth_required
|
||||||
from fastapi.security import HTTPBearer
|
from fastapi.security import HTTPBearer
|
||||||
|
|
||||||
from apis.endpoints import sns
|
|
||||||
|
|
||||||
# Import the security scheme
|
# Import the security scheme
|
||||||
bearer_scheme = HTTPBearer(scheme_name="Bearer Authentication")
|
bearer_scheme = HTTPBearer(scheme_name="Bearer Authentication")
|
||||||
|
|
||||||
from .endpoints import clinics, doctors, calender, appointments, patients, admin, auth, s3, users, clinicDoctor, dashboard, call_transcripts, notifications,sns
|
from .endpoints import clinics, doctors, calender, appointments, patients, admin, auth, s3, users, clinicDoctor, dashboard, call_transcripts, notifications,sns, stripe
|
||||||
|
|
||||||
api_router = APIRouter()
|
api_router = APIRouter()
|
||||||
# api_router.include_router(twilio.router, prefix="/twilio")
|
# api_router.include_router(twilio.router, prefix="/twilio")
|
||||||
|
|
@ -23,6 +21,8 @@ api_router.include_router(patients.router, prefix="/patients", tags=["patients"]
|
||||||
|
|
||||||
api_router.include_router(sns.router, prefix="/sns", tags=["sns"], include_in_schema=False)
|
api_router.include_router(sns.router, prefix="/sns", tags=["sns"], include_in_schema=False)
|
||||||
|
|
||||||
|
api_router.include_router(stripe.router, prefix="/stripe", tags=["stripe"])
|
||||||
|
|
||||||
api_router.include_router(
|
api_router.include_router(
|
||||||
admin.router,
|
admin.router,
|
||||||
prefix="/admin",
|
prefix="/admin",
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ async def login(data: AuthBase):
|
||||||
|
|
||||||
@router.post("/register")
|
@router.post("/register")
|
||||||
async def register(user_data: UserCreate, background_tasks: BackgroundTasks):
|
async def register(user_data: UserCreate, background_tasks: BackgroundTasks):
|
||||||
token = await AuthService().register(user_data, background_tasks)
|
response = await AuthService().register(user_data, background_tasks)
|
||||||
return ApiResponse(
|
return ApiResponse(
|
||||||
data=token,
|
data=response,
|
||||||
message="User registered successfully"
|
message="User registered successfully"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from services.stripeServices import StripeServices
|
||||||
|
|
||||||
|
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-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("/webhook")
|
||||||
|
async def stripe_webhook(request: Request):
|
||||||
|
return await stripe_service.handle_webhook(request)
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import dotenv
|
import dotenv
|
||||||
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
import os
|
import os
|
||||||
|
|
||||||
dotenv.load_dotenv()
|
|
||||||
|
|
||||||
engine = create_engine(
|
engine = create_engine(
|
||||||
os.getenv("DB_URL"),
|
os.getenv("DB_URL"),
|
||||||
|
|
@ -13,6 +14,7 @@ engine = create_engine(
|
||||||
max_overflow=10, # Max extra connections when pool is full
|
max_overflow=10, # Max extra connections when pool is full
|
||||||
pool_recycle=3600, # Recycle connections after 1 hour
|
pool_recycle=3600, # Recycle connections after 1 hour
|
||||||
echo=True, # Log SQL queries
|
echo=True, # Log SQL queries
|
||||||
|
connect_args={"sslmode": "require" if os.getenv("IS_DEV") == "False" else "disable"},
|
||||||
)
|
)
|
||||||
|
|
||||||
Base = declarative_base() # Base class for ORM models
|
Base = declarative_base() # Base class for ORM models
|
||||||
|
|
|
||||||
27
main.py
27
main.py
|
|
@ -1,9 +1,13 @@
|
||||||
|
import os
|
||||||
import dotenv
|
import dotenv
|
||||||
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
import logging
|
import logging
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.security import HTTPBearer
|
from fastapi.security import HTTPBearer
|
||||||
|
import stripe
|
||||||
|
|
||||||
# db
|
# db
|
||||||
from database import Base, engine
|
from database import Base, engine
|
||||||
|
|
@ -16,7 +20,6 @@ from middleware.ErrorHandlerMiddleware import ErrorHandlerMiddleware, configure_
|
||||||
from middleware.CustomRequestTypeMiddleware import TextPlainMiddleware
|
from middleware.CustomRequestTypeMiddleware import TextPlainMiddleware
|
||||||
from services.emailService import EmailService
|
from services.emailService import EmailService
|
||||||
|
|
||||||
dotenv.load_dotenv()
|
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
|
|
@ -26,12 +29,34 @@ logging.basicConfig(
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
STRIPE_SECRET_KEY = os.getenv("STRIPE_SECRET_KEY")
|
||||||
|
STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET")
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
logger.info("Starting application")
|
logger.info("Starting application")
|
||||||
try:
|
try:
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
logger.info("Created database tables")
|
logger.info("Created database tables")
|
||||||
|
|
||||||
|
if STRIPE_SECRET_KEY is None or STRIPE_WEBHOOK_SECRET is None:
|
||||||
|
raise ValueError("Stripe API key or webhook secret is not set")
|
||||||
|
|
||||||
|
stripe.api_key = STRIPE_SECRET_KEY
|
||||||
|
stripe.api_version = "2025-04-30.basil"
|
||||||
|
logger.info("Stripe API key set")
|
||||||
|
|
||||||
|
# Test Stripe connection
|
||||||
|
try:
|
||||||
|
account = stripe.Account.retrieve()
|
||||||
|
logger.info(f"Stripe connection verified - Account ID: {account.id}")
|
||||||
|
except stripe.error.AuthenticationError as e:
|
||||||
|
logger.error(f"Stripe authentication failed: {e}")
|
||||||
|
raise
|
||||||
|
except stripe.error.StripeError as e:
|
||||||
|
logger.error(f"Stripe connection test failed: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating database tables: {e}")
|
logger.error(f"Error creating database tables: {e}")
|
||||||
raise e
|
raise e
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
from database import Base
|
||||||
|
from models.CustomBase import CustomBase
|
||||||
|
from sqlalchemy import Column, Integer, String, ForeignKey
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
class PaymentLogs(Base, CustomBase):
|
||||||
|
__tablename__ = "payment_logs"
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
customer_id = Column(String)
|
||||||
|
account_id = Column(String)
|
||||||
|
amount = Column(Integer)
|
||||||
|
clinic_id = Column(Integer)
|
||||||
|
unique_clinic_id = Column(String)
|
||||||
|
payment_status = Column(String)
|
||||||
|
metadata_logs = Column(String)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
from database import Base
|
||||||
|
from models.CustomBase import CustomBase
|
||||||
|
from sqlalchemy import Column, Integer, String, ForeignKey
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
class StripeUsers(Base, CustomBase):
|
||||||
|
__tablename__ = "stripe_users"
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
user_id = Column(Integer, ForeignKey('users.id'), nullable=False, unique=True)
|
||||||
|
customer_id = Column(String)
|
||||||
|
account_id = Column(String)
|
||||||
|
|
||||||
|
user = relationship("Users", back_populates="stripe_user")
|
||||||
|
|
@ -25,4 +25,7 @@ class Users(Base, CustomBase):
|
||||||
|
|
||||||
# Clinics created by this user
|
# Clinics created by this user
|
||||||
created_clinics = relationship("Clinics", back_populates="creator")
|
created_clinics = relationship("Clinics", back_populates="creator")
|
||||||
clinic_file_verifications = relationship("ClinicFileVerifications", back_populates="last_changed_by_user")
|
clinic_file_verifications = relationship("ClinicFileVerifications", back_populates="last_changed_by_user")
|
||||||
|
|
||||||
|
# Stripe relationships
|
||||||
|
stripe_user = relationship("StripeUsers", back_populates="user")
|
||||||
|
|
@ -16,6 +16,8 @@ from .ClinicFileVerifications import ClinicFileVerifications
|
||||||
from .OTP import OTP
|
from .OTP import OTP
|
||||||
from .ResetPasswordTokens import ResetPasswordTokens
|
from .ResetPasswordTokens import ResetPasswordTokens
|
||||||
from .ClinicOffers import ClinicOffers
|
from .ClinicOffers import ClinicOffers
|
||||||
|
from .StripeUsers import StripeUsers
|
||||||
|
from .PaymentLogs import PaymentLogs
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Users",
|
"Users",
|
||||||
|
|
@ -35,5 +37,7 @@ __all__ = [
|
||||||
"ClinicFileVerifications",
|
"ClinicFileVerifications",
|
||||||
"OTP",
|
"OTP",
|
||||||
"ResetPasswordTokens",
|
"ResetPasswordTokens",
|
||||||
"ClinicOffers"
|
"ClinicOffers",
|
||||||
|
"StripeUsers",
|
||||||
|
"PaymentLogs"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
|
|
@ -55,17 +55,7 @@ class AuthService:
|
||||||
|
|
||||||
async def register(self, user_data: UserCreate, background_tasks=None):
|
async def register(self, user_data: UserCreate, background_tasks=None):
|
||||||
response = await self.user_service.create_user(user_data, background_tasks)
|
response = await self.user_service.create_user(user_data, background_tasks)
|
||||||
user = {
|
return response
|
||||||
"id": response.id,
|
|
||||||
"username": response.username,
|
|
||||||
"email": response.email,
|
|
||||||
"clinicRole": response.clinicRole,
|
|
||||||
"userType": response.userType,
|
|
||||||
"mobile": response.mobile,
|
|
||||||
"clinicId": response.created_clinics[0].id
|
|
||||||
}
|
|
||||||
token = create_jwt_token(user)
|
|
||||||
return token
|
|
||||||
|
|
||||||
def blockEmailSNS(self, body: str):
|
def blockEmailSNS(self, body: str):
|
||||||
# confirm subscription
|
# confirm subscription
|
||||||
|
|
@ -172,7 +162,7 @@ class AuthService:
|
||||||
raise ValidationException("User with same email already exists")
|
raise ValidationException("User with same email already exists")
|
||||||
|
|
||||||
user = Users(
|
user = Users(
|
||||||
username=data.username.lower(),
|
username=data.username,
|
||||||
email=data.email.lower(),
|
email=data.email.lower(),
|
||||||
password=hashed_password,
|
password=hashed_password,
|
||||||
userType=UserType.SUPER_ADMIN,
|
userType=UserType.SUPER_ADMIN,
|
||||||
|
|
|
||||||
|
|
@ -157,13 +157,16 @@ class ClinicDoctorsServices:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
async def delete_clinic_doctor(self, clinic_doctor_id: int):
|
async def delete_clinic_doctor(self, clinic_doctor_id: int):
|
||||||
clinic_doctor = (
|
try:
|
||||||
self.db.query(ClinicDoctors)
|
clinic_doctor = (
|
||||||
.filter(ClinicDoctors.id == clinic_doctor_id)
|
self.db.query(ClinicDoctors)
|
||||||
.first()
|
.filter(ClinicDoctors.id == clinic_doctor_id)
|
||||||
)
|
.first()
|
||||||
self.db.delete(clinic_doctor)
|
)
|
||||||
self.db.commit()
|
self.db.delete(clinic_doctor)
|
||||||
|
self.db.commit()
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
async def get_doctor_status_count(self):
|
async def get_doctor_status_count(self):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -265,6 +265,10 @@ class ClinicServices:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
async def get_clinic_offer_by_clinic_email(self, clinic_email: str):
|
||||||
|
clinic_offer = self.db.query(ClinicOffers).filter(ClinicOffers.clinic_email == clinic_email).first()
|
||||||
|
return clinic_offer
|
||||||
|
|
||||||
async def get_clinic_offers(self, user, limit:int, offset:int, search:str = ""):
|
async def get_clinic_offers(self, user, limit:int, offset:int, search:str = ""):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import dotenv
|
||||||
|
|
||||||
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
|
from models import Clinics
|
||||||
|
import uuid
|
||||||
|
from fastapi import Request
|
||||||
|
from datetime import datetime
|
||||||
|
from models import PaymentLogs
|
||||||
|
from enums.enums import ClinicStatus
|
||||||
|
|
||||||
|
from database import get_db
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
import stripe
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
|
class StripeServices:
|
||||||
|
def __init__(self):
|
||||||
|
self.db: Session = next(get_db())
|
||||||
|
self.logger = logger
|
||||||
|
self.webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET")
|
||||||
|
|
||||||
|
async def create_customer(self, user_id: int, email: str, name: str):
|
||||||
|
try:
|
||||||
|
customer = stripe.Customer.create(
|
||||||
|
email=email, name=name, metadata={"user_id": user_id}
|
||||||
|
)
|
||||||
|
return customer
|
||||||
|
except stripe.error.StripeError as e:
|
||||||
|
self.logger.error(f"Error creating customer: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def delete_customer(self, customer_id: str):
|
||||||
|
try:
|
||||||
|
stripe.Customer.delete(customer_id)
|
||||||
|
except stripe.error.StripeError as e:
|
||||||
|
self.logger.error(f"Error deleting customer: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def create_account(self, user_id: int, email: str, name: str, phone: str):
|
||||||
|
try:
|
||||||
|
account = stripe.Account.create(
|
||||||
|
type="express",
|
||||||
|
country="AU",
|
||||||
|
capabilities={
|
||||||
|
"card_payments": {"requested": True},
|
||||||
|
"transfers": {"requested": True},
|
||||||
|
},
|
||||||
|
business_type="individual",
|
||||||
|
individual={"first_name": name, "email": email},
|
||||||
|
metadata={"user_id": user_id},
|
||||||
|
)
|
||||||
|
return account
|
||||||
|
except stripe.error.StripeError as e:
|
||||||
|
self.logger.error(f"Error creating account: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def delete_account(self, account_id: str):
|
||||||
|
try:
|
||||||
|
stripe.Account.delete(account_id)
|
||||||
|
except stripe.error.StripeError as e:
|
||||||
|
self.logger.error(f"Error deleting account: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def create_checkout_session(self, user_id: int):
|
||||||
|
try:
|
||||||
|
checkout_session = stripe.checkout.Session.create(
|
||||||
|
payment_method_types=["card"],
|
||||||
|
line_items=[
|
||||||
|
{
|
||||||
|
"price_data": {
|
||||||
|
"currency": "aud",
|
||||||
|
"product_data": {
|
||||||
|
"name": "Willio Voice Subscription",
|
||||||
|
},
|
||||||
|
"unit_amount": 5000,
|
||||||
|
},
|
||||||
|
"quantity": 1,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
expand=["payment_intent"],
|
||||||
|
mode="payment",
|
||||||
|
payment_intent_data={"metadata": {"order_id": "1"}},
|
||||||
|
success_url="http://54.79.156.66/",
|
||||||
|
cancel_url="http://54.79.156.66/",
|
||||||
|
metadata={"user_id": user_id},
|
||||||
|
)
|
||||||
|
return checkout_session
|
||||||
|
except stripe.error.StripeError as e:
|
||||||
|
self.logger.error(f"Error creating checkout session: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def create_setup_fees(self, customer_id: str, amount: int):
|
||||||
|
try:
|
||||||
|
setup_intent = stripe.InvoiceItem.create(
|
||||||
|
customer=customer_id,
|
||||||
|
amount=amount,
|
||||||
|
currency="aud",
|
||||||
|
description="Setup Fees",
|
||||||
|
)
|
||||||
|
return setup_intent
|
||||||
|
except stripe.error.StripeError as e:
|
||||||
|
self.logger.error(f"Error creating setup intent: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def create_subscription_checkout(self, fees_to_be: dict, clinic_id: int, account_id: str, customer_id: str):
|
||||||
|
try:
|
||||||
|
|
||||||
|
unique_id = str(uuid.uuid4())
|
||||||
|
unique_clinic_id = f"clinic_{clinic_id}_{unique_id}"
|
||||||
|
|
||||||
|
line_items = [{
|
||||||
|
'price_data': {
|
||||||
|
'currency': 'aud',
|
||||||
|
'product_data': {
|
||||||
|
'name': 'Monthly Subscription',
|
||||||
|
},
|
||||||
|
'unit_amount': fees_to_be["subscription_fees"],
|
||||||
|
'recurring': {
|
||||||
|
'interval': 'year',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'quantity': 1,
|
||||||
|
}]
|
||||||
|
|
||||||
|
line_items.append({
|
||||||
|
'price_data': {
|
||||||
|
'currency': 'aud',
|
||||||
|
'product_data': {
|
||||||
|
'name': 'Per Call',
|
||||||
|
},
|
||||||
|
'unit_amount': fees_to_be["per_call_charges"],
|
||||||
|
},
|
||||||
|
'quantity': 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
line_items.append({
|
||||||
|
'price_data': {
|
||||||
|
'currency': 'aud',
|
||||||
|
'product_data': {
|
||||||
|
'name': 'Setup Fee',
|
||||||
|
},
|
||||||
|
'unit_amount': fees_to_be["setup_fees"],
|
||||||
|
},
|
||||||
|
'quantity': 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
metadata = {
|
||||||
|
"clinic_id": clinic_id,
|
||||||
|
"unique_clinic_id": unique_clinic_id,
|
||||||
|
"account_id": account_id,
|
||||||
|
"customer_id": customer_id
|
||||||
|
}
|
||||||
|
|
||||||
|
session_data = {
|
||||||
|
'customer': customer_id,
|
||||||
|
'payment_method_types': ['card'],
|
||||||
|
'mode': 'subscription',
|
||||||
|
'line_items': line_items,
|
||||||
|
'success_url': 'http://54.79.156.66/',
|
||||||
|
'cancel_url': 'http://54.79.156.66/',
|
||||||
|
'metadata': metadata,
|
||||||
|
'subscription_data': {
|
||||||
|
'metadata': metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session = stripe.checkout.Session.create(**session_data)
|
||||||
|
|
||||||
|
payment_log = PaymentLogs(
|
||||||
|
customer_id=customer_id,
|
||||||
|
account_id=account_id,
|
||||||
|
amount=fees_to_be["total"],
|
||||||
|
clinic_id=clinic_id,
|
||||||
|
unique_clinic_id=unique_clinic_id,
|
||||||
|
payment_status="pending",
|
||||||
|
metadata_logs=json.dumps(metadata)
|
||||||
|
)
|
||||||
|
self.db.add(payment_log)
|
||||||
|
self.db.commit()
|
||||||
|
|
||||||
|
return session
|
||||||
|
except stripe.error.StripeError as e:
|
||||||
|
self.logger.error(f"Error creating checkout session: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_webhook(self, request: Request):
|
||||||
|
try:
|
||||||
|
payload = await request.body()
|
||||||
|
event = stripe.Webhook.construct_event(
|
||||||
|
payload, request.headers.get("Stripe-Signature"), self.webhook_secret
|
||||||
|
)
|
||||||
|
self.logger.info(f"Stripe webhook event type: {event['type']}")
|
||||||
|
|
||||||
|
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"])
|
||||||
|
|
||||||
|
elif event["type"] == "checkout.session.completed":
|
||||||
|
await self.update_payment_log(event["data"]["object"]["metadata"]["unique_clinic_id"], event["data"]["object"]["metadata"]["clinic_id"])
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
async 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()
|
||||||
|
if payment_log:
|
||||||
|
payment_log.payment_status = "success"
|
||||||
|
self.db.commit()
|
||||||
|
|
||||||
|
clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first()
|
||||||
|
if clinic:
|
||||||
|
clinic.status = ClinicStatus.UNDER_REVIEW
|
||||||
|
self.db.commit()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error updating payment log: {e}")
|
||||||
|
raise
|
||||||
|
|
@ -10,21 +10,29 @@ from enums.enums import ClinicStatus, UserType
|
||||||
from schemas.UpdateSchemas import UserUpdate
|
from schemas.UpdateSchemas import UserUpdate
|
||||||
from exceptions.unauthorized_exception import UnauthorizedException
|
from exceptions.unauthorized_exception import UnauthorizedException
|
||||||
from interface.common_response import CommonResponse
|
from interface.common_response import CommonResponse
|
||||||
from exceptions.business_exception import BusinessValidationException
|
from models import ClinicFileVerifications, StripeUsers
|
||||||
from models import ClinicFileVerifications
|
from services.stripeServices import StripeServices
|
||||||
from utils.password_utils import hash_password
|
from utils.password_utils import hash_password
|
||||||
from schemas.CreateSchemas import UserCreate
|
from schemas.CreateSchemas import UserCreate
|
||||||
from exceptions.resource_not_found_exception import ResourceNotFoundException
|
from exceptions.resource_not_found_exception import ResourceNotFoundException
|
||||||
from exceptions.db_exceptions import DBExceptionHandler
|
from exceptions.db_exceptions import DBExceptionHandler
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
from services.emailService import EmailService
|
from services.emailService import EmailService
|
||||||
|
from services.clinicServices import ClinicServices
|
||||||
|
from services.dashboardService import DashboardService
|
||||||
class UserServices:
|
class UserServices:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.db: Session = next(get_db())
|
self.db: Session = next(get_db())
|
||||||
self.email_service = EmailService()
|
self.email_service = EmailService()
|
||||||
|
self.stripe_service = StripeServices()
|
||||||
|
self.clinic_service = ClinicServices()
|
||||||
|
self.dashboard_service = DashboardService()
|
||||||
|
|
||||||
async def create_user(self, user_data: UserCreate, background_tasks=None):
|
async def create_user(self, user_data: UserCreate, background_tasks=None):
|
||||||
|
|
||||||
|
stripe_customer = None
|
||||||
|
stripe_account = None
|
||||||
|
|
||||||
# Start a transaction
|
# Start a transaction
|
||||||
try:
|
try:
|
||||||
user = user_data.user
|
user = user_data.user
|
||||||
|
|
@ -53,6 +61,20 @@ class UserServices:
|
||||||
# Add user to database but don't commit yet
|
# Add user to database but don't commit yet
|
||||||
self.db.add(new_user)
|
self.db.add(new_user)
|
||||||
self.db.flush() # Flush to get the user ID without committing
|
self.db.flush() # Flush to get the user ID without committing
|
||||||
|
|
||||||
|
# Create stripe customer
|
||||||
|
stripe_customer = await self.stripe_service.create_customer(new_user.id, user.email, user.username)
|
||||||
|
|
||||||
|
# Create stripe account
|
||||||
|
stripe_account = await self.stripe_service.create_account(new_user.id, user.email, user.username, user.mobile)
|
||||||
|
|
||||||
|
# Create stripe user
|
||||||
|
stripe_user = StripeUsers(
|
||||||
|
user_id=new_user.id,
|
||||||
|
customer_id=stripe_customer.id,
|
||||||
|
account_id=stripe_account.id
|
||||||
|
)
|
||||||
|
self.db.add(stripe_user)
|
||||||
|
|
||||||
# Get clinic data
|
# Get clinic data
|
||||||
clinic = user_data.clinic
|
clinic = user_data.clinic
|
||||||
|
|
@ -64,9 +86,7 @@ class UserServices:
|
||||||
|
|
||||||
if existing_clinic:
|
if existing_clinic:
|
||||||
# This will trigger rollback in the exception handler
|
# This will trigger rollback in the exception handler
|
||||||
raise ValidationException("Clinic with same domain already exists")
|
raise ValidationException("Clinic with same domain already exists")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Create clinic instance
|
# Create clinic instance
|
||||||
new_clinic = Clinics(
|
new_clinic = Clinics(
|
||||||
|
|
@ -94,7 +114,7 @@ class UserServices:
|
||||||
voice_model_gender=clinic.voice_model_gender,
|
voice_model_gender=clinic.voice_model_gender,
|
||||||
scenarios=clinic.scenarios,
|
scenarios=clinic.scenarios,
|
||||||
general_info=clinic.general_info,
|
general_info=clinic.general_info,
|
||||||
status=ClinicStatus.UNDER_REVIEW, #TODO: change this to PAYMENT_DUE
|
status=ClinicStatus.PAYMENT_DUE, #TODO: change this to PAYMENT_DUE
|
||||||
domain=domain,
|
domain=domain,
|
||||||
creator_id=new_user.id, # Set the creator_id to link the clinic to the user who created it
|
creator_id=new_user.id, # Set the creator_id to link the clinic to the user who created it
|
||||||
)
|
)
|
||||||
|
|
@ -119,17 +139,42 @@ class UserServices:
|
||||||
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
|
# If no background_tasks provided, we don't send emails
|
||||||
|
|
||||||
return new_user
|
offer = await self.clinic_service.get_clinic_offer_by_clinic_email(clinic.email)
|
||||||
|
|
||||||
|
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 offer:
|
||||||
|
fees_to_be["setup_fees"] = offer.setup_fees
|
||||||
|
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)
|
||||||
|
|
||||||
|
return payment_link.url
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating user: {str(e)}")
|
logger.error(f"Error creating user: {str(e)}")
|
||||||
# Rollback the transaction if any error occurs
|
# Rollback the transaction if any error occurs
|
||||||
self.db.rollback()
|
self.db.rollback()
|
||||||
|
|
||||||
|
# Delete stripe customer and account
|
||||||
|
if stripe_customer:
|
||||||
|
await self.stripe_service.delete_customer(stripe_customer.id)
|
||||||
|
if stripe_account:
|
||||||
|
await self.stripe_service.delete_account(stripe_account.id)
|
||||||
|
|
||||||
# Use the centralized exception handler
|
# Use the centralized exception handler
|
||||||
DBExceptionHandler.handle_exception(e, context="creating user")
|
DBExceptionHandler.handle_exception(e, context="creating user")
|
||||||
finally:
|
finally:
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
|
|
||||||
async def get_user(self, user_id) -> UserResponse:
|
async def get_user(self, user_id) -> UserResponse:
|
||||||
try:
|
try:
|
||||||
# Query the user by ID and explicitly load the created clinics relationship
|
# Query the user by ID and explicitly load the created clinics relationship
|
||||||
|
|
@ -254,4 +299,13 @@ class UserServices:
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log the error but don't interrupt the main flow
|
# Log the error but don't interrupt the main flow
|
||||||
logger.error(f"Error sending admin emails: {str(e)}")
|
logger.error(f"Error sending admin emails: {str(e)}")
|
||||||
|
|
||||||
|
async def create_payment_link(self, user_id: int):
|
||||||
|
user = self.db.query(Users).filter(Users.id == user_id).first()
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
logger.error("User not found")
|
||||||
|
raise ResourceNotFoundException("User not found")
|
||||||
|
|
||||||
|
return self.stripe_service.create_payment_link(user_id)
|
||||||
Loading…
Reference in New Issue