parent
1a0109bebd
commit
532e0a3288
|
|
@ -26,6 +26,12 @@ stripe_service = StripeServices()
|
|||
# )
|
||||
|
||||
|
||||
@router.get("/get-invoice", dependencies=[Depends(auth_required)])
|
||||
async def get_invoice(req:Request):
|
||||
invoice_url = await stripe_service.get_invoice(req.state.user)
|
||||
return ApiResponse(data=invoice_url, message="Invoice URL retrieved successfully")
|
||||
|
||||
|
||||
@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)
|
||||
|
|
@ -33,4 +39,5 @@ async def create_payment_session(req:Request):
|
|||
|
||||
@router.post("/webhook")
|
||||
async def stripe_webhook(request: Request):
|
||||
return await stripe_service.handle_webhook(request)
|
||||
await stripe_service.handle_webhook(request)
|
||||
return "OK"
|
||||
|
|
@ -19,6 +19,7 @@ class ClinicStatus(Enum):
|
|||
REQUESTED_DOC = "requested_doc"
|
||||
REJECTED = "rejected"
|
||||
PAYMENT_DUE = "payment_due"
|
||||
SUBSCRIPTION_ENDED = "subscription_ended"
|
||||
|
||||
class ClinicUserRoles(Enum):
|
||||
DIRECTOR = "director"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
"""updated_enums_clinic_status
|
||||
|
||||
Revision ID: 5ed8ac3d258c
|
||||
Revises: a19fede0cdc6
|
||||
Create Date: 2025-06-02 11:11:56.589321
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '5ed8ac3d258c'
|
||||
down_revision: Union[str, None] = 'a19fede0cdc6'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
# Add new status to clinicstatus enum
|
||||
op.execute("ALTER TYPE clinicstatus ADD VALUE IF NOT EXISTS 'SUBSCRIPTION_ENDED' AFTER 'PAYMENT_DUE'")
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
# Note: In PostgreSQL, you cannot directly remove an enum value.
|
||||
# You would need to create a new enum type, update the column to use the new type,
|
||||
# and then drop the old type. This is a complex operation and might not be needed.
|
||||
# The upgrade will be reverted when applying previous migrations.
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
from sqlalchemy import Column, Integer, String
|
||||
from database import Base
|
||||
from .CustomBase import CustomBase
|
||||
|
||||
class Subscriptions(Base, CustomBase):
|
||||
__tablename__ = "subscriptions"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
session_id = Column(String(255), index=True)
|
||||
customer_id = Column(String,index=True)
|
||||
account_id = Column(String,index=True)
|
||||
subscription_id = Column(String,index=True)
|
||||
clinic_id = Column(Integer, index=True)
|
||||
status = Column(String)
|
||||
current_period_start = Column(String) # unix timestamp
|
||||
current_period_end = Column(String) # unix timestamp
|
||||
metadata_logs = Column(String)
|
||||
|
|
@ -19,6 +19,7 @@ from .ClinicOffers import ClinicOffers
|
|||
from .StripeUsers import StripeUsers
|
||||
from .PaymentLogs import PaymentLogs
|
||||
from .PaymentSessions import PaymentSessions
|
||||
from .Subscriptions import Subscriptions
|
||||
|
||||
__all__ = [
|
||||
"Users",
|
||||
|
|
@ -41,5 +42,6 @@ __all__ = [
|
|||
"ClinicOffers",
|
||||
"StripeUsers",
|
||||
"PaymentLogs",
|
||||
"PaymentSessions"
|
||||
"PaymentSessions",
|
||||
"Subscriptions"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -55,8 +55,36 @@ class AuthService:
|
|||
return token
|
||||
|
||||
async def register(self, user_data: UserCreate, background_tasks=None):
|
||||
response = await self.user_service.create_user(user_data, background_tasks)
|
||||
return response
|
||||
try:
|
||||
resp = await self.user_service.create_user(user_data, background_tasks)
|
||||
|
||||
# Get the SQLAlchemy model instance
|
||||
user_obj = resp["user"]
|
||||
|
||||
# create token with user data
|
||||
user_data = {
|
||||
"id": user_obj["id"],
|
||||
"username": user_obj["username"],
|
||||
"email": user_obj["email"],
|
||||
"clinicRole": user_obj["clinicRole"],
|
||||
"userType": user_obj["userType"],
|
||||
"mobile": user_obj["mobile"],
|
||||
"clinicId": user_obj["clinicId"]
|
||||
}
|
||||
token = create_jwt_token(user_data)
|
||||
|
||||
# Update response with token
|
||||
resp["token"] = token
|
||||
|
||||
response = {
|
||||
"url": resp.get("url"),
|
||||
"token": token
|
||||
}
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error registering user: {e}")
|
||||
raise e
|
||||
|
||||
def blockEmailSNS(self, body: str):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@ class ClinicServices:
|
|||
count_query = text("""
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
COUNT(CASE WHEN status = 'ACTIVE' OR status = 'INACTIVE' THEN 1 END) as active,
|
||||
COUNT(CASE WHEN status != 'ACTIVE' AND status != 'INACTIVE' THEN 1 END) as rejected
|
||||
COUNT(CASE WHEN status = 'ACTIVE' OR status = 'INACTIVE' OR 'SUBSCRIPTION_ENDED' THEN 1 END) as active,
|
||||
COUNT(CASE WHEN status != 'ACTIVE' AND status != 'INACTIVE' AND status != 'SUBSCRIPTION_ENDED' THEN 1 END) as rejected
|
||||
FROM clinics
|
||||
""")
|
||||
|
||||
|
|
@ -127,6 +127,8 @@ class ClinicServices:
|
|||
finally:
|
||||
self.db.close()
|
||||
|
||||
|
||||
|
||||
async def get_clinic_files(self, clinic_id: int):
|
||||
try:
|
||||
clinic_files = self.db.query(ClinicFileVerifications).filter(ClinicFileVerifications.clinic_id == clinic_id).first()
|
||||
|
|
@ -166,7 +168,18 @@ class ClinicServices:
|
|||
self.db.refresh(clinic)
|
||||
|
||||
clinic_response = Clinic(**clinic.__dict__.copy())
|
||||
clinic_response.logo = get_signed_url(clinic_response.logo) if clinic_response.logo else None
|
||||
|
||||
# update clinic files
|
||||
clinic_files = await self.get_clinic_files(clinic_id)
|
||||
|
||||
if clinic_data.abn_doc:
|
||||
clinic_files.abn_doc_is_verified = None
|
||||
if clinic_data.contract_doc:
|
||||
clinic_files.contract_doc_is_verified = None
|
||||
|
||||
self.db.add(clinic_files)
|
||||
self.db.commit()
|
||||
self.db.refresh(clinic_files)
|
||||
|
||||
return clinic_response
|
||||
except Exception as e:
|
||||
|
|
@ -202,7 +215,8 @@ class ClinicServices:
|
|||
ClinicStatus.INACTIVE,
|
||||
ClinicStatus.UNDER_REVIEW,
|
||||
ClinicStatus.REJECTED,
|
||||
ClinicStatus.PAYMENT_DUE
|
||||
ClinicStatus.PAYMENT_DUE,
|
||||
ClinicStatus.SUBSCRIPTION_ENDED
|
||||
])
|
||||
).group_by(Clinics.status).all()
|
||||
|
||||
|
|
@ -212,7 +226,7 @@ class ClinicServices:
|
|||
"totalActiveClinics": 0,
|
||||
"totalUnderReviewClinics": 0,
|
||||
"totalRejectedClinics": 0,
|
||||
"totalPaymentDueClinics": 0
|
||||
"totalPaymentDueClinics": 0,
|
||||
}
|
||||
|
||||
# Map status values to their respective count keys
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ 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
|
||||
from models import Clinics,PaymentSessions, Subscriptions
|
||||
import uuid
|
||||
from fastapi import Request
|
||||
from datetime import datetime
|
||||
|
|
@ -75,6 +75,42 @@ class StripeServices:
|
|||
self.logger.error(f"Error deleting account: {e}")
|
||||
raise
|
||||
|
||||
async def get_invoice(self, user):
|
||||
try:
|
||||
|
||||
if user["userType"] != UserType.CLINIC_ADMIN:
|
||||
raise UnauthorizedException("User is not authorized to perform this action")
|
||||
|
||||
clinic = self.db.query(Clinics).filter(Clinics.creator_id == user["id"]).first()
|
||||
|
||||
if not clinic:
|
||||
raise ResourceNotFoundException("Clinic not found!")
|
||||
|
||||
customer = self.db.query(StripeUsers).filter(StripeUsers.user_id == user["id"]).first()
|
||||
|
||||
if not customer:
|
||||
raise ResourceNotFoundException("Customer not found!")
|
||||
|
||||
subscription = self.db.query(Subscriptions).filter(Subscriptions.clinic_id == clinic.id, Subscriptions.customer_id == customer.customer_id, Subscriptions.status == "active").first()
|
||||
|
||||
if not subscription:
|
||||
raise ResourceNotFoundException("Subscription not found!")
|
||||
|
||||
stripe_subscription = stripe.Subscription.retrieve(subscription.subscription_id)
|
||||
|
||||
invoice = stripe.Invoice.retrieve(
|
||||
stripe_subscription["latest_invoice"]
|
||||
)
|
||||
return invoice.hosted_invoice_url
|
||||
except stripe.error.StripeError as e:
|
||||
self.logger.error(f"Error getting invoice: {e}")
|
||||
raise
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting invoice: {e}")
|
||||
raise
|
||||
finally:
|
||||
self.db.close()
|
||||
|
||||
async def create_payment_session(self, user):
|
||||
try:
|
||||
if user["userType"] != UserType.CLINIC_ADMIN:
|
||||
|
|
@ -105,9 +141,6 @@ class StripeServices:
|
|||
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)
|
||||
|
||||
|
|
@ -138,8 +171,8 @@ class StripeServices:
|
|||
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/",
|
||||
success_url="http://54.79.156.66/auth/waiting",
|
||||
cancel_url="http://54.79.156.66/auth/waiting",
|
||||
metadata={"user_id": user_id},
|
||||
)
|
||||
return checkout_session
|
||||
|
|
@ -253,7 +286,6 @@ class StripeServices:
|
|||
finally:
|
||||
self.db.close()
|
||||
|
||||
|
||||
async def handle_webhook(self, request: Request):
|
||||
try:
|
||||
payload = await request.body()
|
||||
|
|
@ -262,24 +294,31 @@ class StripeServices:
|
|||
)
|
||||
self.logger.info(f"Stripe webhook event type: {event['type']}")
|
||||
|
||||
if event["type"] == "invoice.payment_succeeded":
|
||||
if event["type"] == "checkout.session.expired":
|
||||
pass
|
||||
|
||||
if event["type"] == "checkout.session.async_payment_succeeded":
|
||||
self.logger.info("Async payment succeeded")
|
||||
|
||||
elif event["type"] == "checkout.session.completed":
|
||||
if event["type"] == "checkout.session.completed":
|
||||
unique_clinic_id = event["data"]["object"]["metadata"]["unique_clinic_id"]
|
||||
clinic_id = event["data"]["object"]["metadata"]["clinic_id"]
|
||||
customer_id = event["data"]["object"]["metadata"]["customer_id"]
|
||||
account_id = event["data"]["object"]["metadata"]["account_id"]
|
||||
total = event["data"]["object"]["amount_total"]
|
||||
metadata = event["data"]["object"]["metadata"]
|
||||
self.update_payment_log(unique_clinic_id, clinic_id, customer_id, account_id, total, metadata)
|
||||
session_id = event["data"]["object"]["id"]
|
||||
subscription_id = event["data"]["object"]["subscription"]
|
||||
|
||||
self._update_payment_log(unique_clinic_id, clinic_id, customer_id, account_id, total, metadata, session_id)
|
||||
|
||||
self._create_subscription_entry({
|
||||
"clinic_id": clinic_id,
|
||||
"customer_id": customer_id,
|
||||
"account_id": account_id,
|
||||
"session_id": session_id,
|
||||
"subscription_id": subscription_id,
|
||||
})
|
||||
# TODO: handle subscription period end
|
||||
|
||||
return event
|
||||
return "OK"
|
||||
except ValueError as e:
|
||||
self.logger.error(f"Invalid payload: {e}")
|
||||
except stripe.error.SignatureVerificationError as e:
|
||||
|
|
@ -287,7 +326,7 @@ class StripeServices:
|
|||
finally:
|
||||
self.db.close()
|
||||
|
||||
def update_payment_log(self, unique_clinic_id:str, clinic_id:int, customer_id:str, account_id:str, total:float, metadata:any):
|
||||
def _update_payment_log(self, unique_clinic_id:str, clinic_id:int, customer_id:str, account_id:str, total:float, metadata:any, session_id:str):
|
||||
try:
|
||||
self.db.query(PaymentSessions).filter(PaymentSessions.clinic_id == clinic_id).delete()
|
||||
|
||||
|
|
@ -301,15 +340,48 @@ class StripeServices:
|
|||
metadata_logs=json.dumps(metadata.to_dict())
|
||||
)
|
||||
self.db.add(payment_log)
|
||||
self.db.commit()
|
||||
|
||||
clinic = self.db.query(Clinics).filter(Clinics.id == clinic_id).first()
|
||||
|
||||
if clinic:
|
||||
clinic.status = ClinicStatus.UNDER_REVIEW
|
||||
self.db.add(clinic)
|
||||
self.db.commit()
|
||||
|
||||
return
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error updating payment log: {e}")
|
||||
finally:
|
||||
self.db.close()
|
||||
self.db.commit()
|
||||
self.db.close()
|
||||
|
||||
def _create_subscription_entry(self,data:dict):
|
||||
try:
|
||||
|
||||
subscription = stripe.Subscription.retrieve(data["subscription_id"])
|
||||
|
||||
new_subscription = Subscriptions(
|
||||
clinic_id=data["clinic_id"],
|
||||
customer_id=data["customer_id"],
|
||||
account_id=data["account_id"],
|
||||
session_id=data["session_id"],
|
||||
subscription_id=data["subscription_id"],
|
||||
status=subscription.status,
|
||||
current_period_start=subscription["items"]["data"][0]["current_period_start"],
|
||||
current_period_end=subscription["items"]["data"][0]["current_period_end"],
|
||||
metadata_logs=json.dumps(subscription.metadata)
|
||||
)
|
||||
self.db.add(new_subscription)
|
||||
|
||||
payment_session = PaymentSessions(
|
||||
session_id=data["session_id"],
|
||||
customer_id=data["customer_id"],
|
||||
clinic_id=data["clinic_id"],
|
||||
status="paid"
|
||||
)
|
||||
self.db.add(payment_session)
|
||||
return
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating subscription entry: {e}")
|
||||
finally:
|
||||
self.db.commit()
|
||||
self.db.close()
|
||||
|
||||
|
|
@ -156,7 +156,24 @@ class UserServices:
|
|||
|
||||
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
|
||||
self.db.commit()
|
||||
|
||||
# Convert the user object to a dictionary before the session is closed
|
||||
user_dict = {
|
||||
"id": new_user.id,
|
||||
"username": new_user.username,
|
||||
"email": new_user.email,
|
||||
"clinicRole": new_user.clinicRole,
|
||||
"userType": new_user.userType,
|
||||
"mobile": new_user.mobile,
|
||||
"clinicId": new_clinic.id
|
||||
}
|
||||
|
||||
return {
|
||||
"url": payment_link.url,
|
||||
"user": user_dict,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating user: {str(e)}")
|
||||
# Rollback the transaction if any error occurs
|
||||
|
|
@ -171,7 +188,6 @@ class UserServices:
|
|||
# Use the centralized exception handler
|
||||
DBExceptionHandler.handle_exception(e, context="creating user")
|
||||
finally:
|
||||
self.db.commit()
|
||||
self.db.close()
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue