From b198b7678eb871bc1641d3df4e41240efa7a3157 Mon Sep 17 00:00:00 2001 From: deepvasoya Date: Tue, 27 May 2025 19:35:07 +0530 Subject: [PATCH] feat: stripe integration --- apis/__init__.py | 6 +- apis/endpoints/auth.py | 4 +- apis/endpoints/stripe.py | 28 ++++ database.py | 4 +- main.py | 27 +++- models/PaymentLogs.py | 16 +++ models/StripeUsers.py | 13 ++ models/Users.py | 5 +- models/__init__.py | 6 +- requirements.txt | Bin 5400 -> 5432 bytes services/authService.py | 14 +- services/clinicDoctorsServices.py | 17 ++- services/clinicServices.py | 4 + services/stripeServices.py | 230 ++++++++++++++++++++++++++++++ services/userServices.py | 74 ++++++++-- 15 files changed, 410 insertions(+), 38 deletions(-) create mode 100644 apis/endpoints/stripe.py create mode 100644 models/PaymentLogs.py create mode 100644 models/StripeUsers.py create mode 100644 services/stripeServices.py diff --git a/apis/__init__.py b/apis/__init__.py index fdc326e..41dbcf8 100644 --- a/apis/__init__.py +++ b/apis/__init__.py @@ -2,12 +2,10 @@ from fastapi import APIRouter, Depends, Security from middleware.auth_dependency import auth_required from fastapi.security import HTTPBearer -from apis.endpoints import sns - # Import the security scheme 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.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(stripe.router, prefix="/stripe", tags=["stripe"]) + api_router.include_router( admin.router, prefix="/admin", diff --git a/apis/endpoints/auth.py b/apis/endpoints/auth.py index 170f1c9..e12bb7e 100644 --- a/apis/endpoints/auth.py +++ b/apis/endpoints/auth.py @@ -18,9 +18,9 @@ async def login(data: AuthBase): @router.post("/register") 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( - data=token, + data=response, message="User registered successfully" ) diff --git a/apis/endpoints/stripe.py b/apis/endpoints/stripe.py new file mode 100644 index 0000000..26b7547 --- /dev/null +++ b/apis/endpoints/stripe.py @@ -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) \ No newline at end of file diff --git a/database.py b/database.py index 3e100eb..b6d2337 100644 --- a/database.py +++ b/database.py @@ -1,10 +1,11 @@ import dotenv +dotenv.load_dotenv() + from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker import os -dotenv.load_dotenv() engine = create_engine( os.getenv("DB_URL"), @@ -13,6 +14,7 @@ engine = create_engine( max_overflow=10, # Max extra connections when pool is full pool_recycle=3600, # Recycle connections after 1 hour 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 diff --git a/main.py b/main.py index 01c2024..b71dab0 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,13 @@ +import os import dotenv +dotenv.load_dotenv() + from fastapi import FastAPI from contextlib import asynccontextmanager import logging from fastapi.middleware.cors import CORSMiddleware from fastapi.security import HTTPBearer +import stripe # db from database import Base, engine @@ -16,7 +20,6 @@ from middleware.ErrorHandlerMiddleware import ErrorHandlerMiddleware, configure_ from middleware.CustomRequestTypeMiddleware import TextPlainMiddleware from services.emailService import EmailService -dotenv.load_dotenv() # Configure logging logging.basicConfig( @@ -26,12 +29,34 @@ logging.basicConfig( logger = logging.getLogger(__name__) +STRIPE_SECRET_KEY = os.getenv("STRIPE_SECRET_KEY") +STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET") + @asynccontextmanager async def lifespan(app: FastAPI): logger.info("Starting application") try: Base.metadata.create_all(bind=engine) 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: logger.error(f"Error creating database tables: {e}") raise e diff --git a/models/PaymentLogs.py b/models/PaymentLogs.py new file mode 100644 index 0000000..f0b1102 --- /dev/null +++ b/models/PaymentLogs.py @@ -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) + \ No newline at end of file diff --git a/models/StripeUsers.py b/models/StripeUsers.py new file mode 100644 index 0000000..c654181 --- /dev/null +++ b/models/StripeUsers.py @@ -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") \ No newline at end of file diff --git a/models/Users.py b/models/Users.py index ba3e76c..081b942 100644 --- a/models/Users.py +++ b/models/Users.py @@ -25,4 +25,7 @@ class Users(Base, CustomBase): # Clinics created by this user created_clinics = relationship("Clinics", back_populates="creator") - clinic_file_verifications = relationship("ClinicFileVerifications", back_populates="last_changed_by_user") \ No newline at end of file + clinic_file_verifications = relationship("ClinicFileVerifications", back_populates="last_changed_by_user") + + # Stripe relationships + stripe_user = relationship("StripeUsers", back_populates="user") \ No newline at end of file diff --git a/models/__init__.py b/models/__init__.py index bd3d5fa..3ed9484 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -16,6 +16,8 @@ from .ClinicFileVerifications import ClinicFileVerifications from .OTP import OTP from .ResetPasswordTokens import ResetPasswordTokens from .ClinicOffers import ClinicOffers +from .StripeUsers import StripeUsers +from .PaymentLogs import PaymentLogs __all__ = [ "Users", @@ -35,5 +37,7 @@ __all__ = [ "ClinicFileVerifications", "OTP", "ResetPasswordTokens", - "ClinicOffers" + "ClinicOffers", + "StripeUsers", + "PaymentLogs" ] diff --git a/requirements.txt b/requirements.txt index 15de012e8d9bb2497a11b5fc0d1e06400529316e..ad429a7050f64b1be092e63d5cf3226576ef2252 100644 GIT binary patch delta 38 rcmbQCwL@#eGa< UserResponse: try: # Query the user by ID and explicitly load the created clinics relationship @@ -254,4 +299,13 @@ class UserServices: ) except Exception as e: # Log the error but don't interrupt the main flow - logger.error(f"Error sending admin emails: {str(e)}") \ No newline at end of file + 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) \ No newline at end of file