From 7f2f73042696102f1fdc41438591ab2177427a2d Mon Sep 17 00:00:00 2001 From: deepvasoya Date: Tue, 3 Jun 2025 19:19:17 +0530 Subject: [PATCH] feat: subscription ended webhook refactor: subscription table --- ...8d19e726b997_updated_subscription_table.py | 42 ++++++++++++ models/Subscriptions.py | 8 ++- services/stripeServices.py | 65 ++++++++++++++----- services/userServices.py | 16 ++--- 4 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 migrations/versions/8d19e726b997_updated_subscription_table.py diff --git a/migrations/versions/8d19e726b997_updated_subscription_table.py b/migrations/versions/8d19e726b997_updated_subscription_table.py new file mode 100644 index 0000000..9bf81ef --- /dev/null +++ b/migrations/versions/8d19e726b997_updated_subscription_table.py @@ -0,0 +1,42 @@ +"""updated_subscription_table + +Revision ID: 8d19e726b997 +Revises: 5ed8ac3d258c +Create Date: 2025-06-03 19:07:17.107577 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '8d19e726b997' +down_revision: Union[str, None] = '5ed8ac3d258c' +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! ### + op.add_column('subscriptions', sa.Column('total', sa.String(), nullable=True)) + op.add_column('subscriptions', sa.Column('setup_fee', sa.String(), nullable=True)) + op.add_column('subscriptions', sa.Column('subscription_fee', sa.String(), nullable=True)) + op.add_column('subscriptions', sa.Column('per_call_charge', sa.String(), nullable=True)) + op.drop_index('ix_subscriptions_session_id', table_name='subscriptions') + op.drop_column('subscriptions', 'session_id') + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('subscriptions', sa.Column('session_id', sa.VARCHAR(length=255), autoincrement=False, nullable=True)) + op.create_index('ix_subscriptions_session_id', 'subscriptions', ['session_id'], unique=False) + op.drop_column('subscriptions', 'per_call_charge') + op.drop_column('subscriptions', 'subscription_fee') + op.drop_column('subscriptions', 'setup_fee') + op.drop_column('subscriptions', 'total') + # ### end Alembic commands ### diff --git a/models/Subscriptions.py b/models/Subscriptions.py index 42013f7..f7b928c 100644 --- a/models/Subscriptions.py +++ b/models/Subscriptions.py @@ -5,9 +5,13 @@ 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) + # session_id = Column(String(255), index=True) customer_id = Column(String,index=True) - account_id = Column(String,index=True) + account_id = Column(String,index=True) + total = Column(String) + setup_fee = Column(String) + subscription_fee = Column(String) + per_call_charge = Column(String) subscription_id = Column(String,index=True) clinic_id = Column(Integer, index=True) status = Column(String) diff --git a/services/stripeServices.py b/services/stripeServices.py index 863cea1..692c6a2 100644 --- a/services/stripeServices.py +++ b/services/stripeServices.py @@ -35,7 +35,7 @@ class StripeServices: async def create_customer(self, user_id: int, email: str, name: str): try: - customer = stripe.Customer.create( + customer = await stripe.Customer.create_async( email=email, name=name, metadata={"user_id": user_id} ) return customer @@ -45,14 +45,14 @@ class StripeServices: async def delete_customer(self, customer_id: str): try: - stripe.Customer.delete(customer_id) + await stripe.Customer.delete_async(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( + account = await stripe.Account.create_async( type="express", country="AU", capabilities={ @@ -70,7 +70,7 @@ class StripeServices: async def delete_account(self, account_id: str): try: - stripe.Account.delete(account_id) + await stripe.Account.delete_async(account_id) except stripe.error.StripeError as e: self.logger.error(f"Error deleting account: {e}") raise @@ -96,9 +96,9 @@ class StripeServices: if not subscription: raise ResourceNotFoundException("Subscription not found!") - stripe_subscription = stripe.Subscription.retrieve(subscription.subscription_id) + stripe_subscription = await stripe.Subscription.retrieve_async(subscription.subscription_id) - invoice = stripe.Invoice.retrieve( + invoice = await stripe.Invoice.retrieve_async( stripe_subscription["latest_invoice"] ) return invoice.hosted_invoice_url @@ -154,7 +154,7 @@ class StripeServices: async def create_checkout_session(self, user_id: int): try: - checkout_session = stripe.checkout.Session.create( + checkout_session = await stripe.checkout.Session.create_async( payment_method_types=["card"], line_items=[ { @@ -182,7 +182,7 @@ class StripeServices: async def create_setup_fees(self, customer_id: str, amount: int): try: - setup_intent = stripe.InvoiceItem.create( + setup_intent = await stripe.InvoiceItem.create_async( customer=customer_id, amount=amount, currency="aud", @@ -256,7 +256,7 @@ class StripeServices: } } - session = stripe.checkout.Session.create(**session_data) + session = await stripe.checkout.Session.create_async(**session_data) payment_log = PaymentLogs( customer_id=customer_id, @@ -294,8 +294,11 @@ class StripeServices: ) self.logger.info(f"Stripe webhook event type: {event['type']}") - if event["type"] == "checkout.session.expired": - pass + if event["type"] == "customer.subscription.deleted": + self.logger.info("customer subscription ended") + subscription_id = event["data"]["object"]["items"]["data"][0]["subscription"] + + await self._subscription_expired(subscription_id) if event["type"] == "checkout.session.completed": unique_clinic_id = event["data"]["object"]["metadata"]["unique_clinic_id"] @@ -307,9 +310,9 @@ class StripeServices: 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) + await self._update_payment_log(unique_clinic_id, clinic_id, customer_id, account_id, total, metadata) - self._create_subscription_entry({ + await self._create_subscription_entry({ "clinic_id": clinic_id, "customer_id": customer_id, "account_id": account_id, @@ -326,7 +329,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, session_id:str): + async def _update_payment_log(self, unique_clinic_id:str, clinic_id:int, customer_id:str, account_id:str, total:float, metadata:any): try: self.db.query(PaymentSessions).filter(PaymentSessions.clinic_id == clinic_id).delete() @@ -353,16 +356,22 @@ class StripeServices: self.db.commit() self.db.close() - def _create_subscription_entry(self,data:dict): + async def _create_subscription_entry(self,data:dict): try: subscription = stripe.Subscription.retrieve(data["subscription_id"]) + metadata_dict = json.loads(subscription.metadata) + fees_to_be = json.loads(metadata_dict["fees_to_be"]) + new_subscription = Subscriptions( clinic_id=data["clinic_id"], customer_id=data["customer_id"], account_id=data["account_id"], - session_id=data["session_id"], + total=fees_to_be["total"], + setup_fee=fees_to_be["setup_fees"], + subscription_fee=fees_to_be["subscription_fees"], + per_call_charge=fees_to_be["per_call_charges"], subscription_id=data["subscription_id"], status=subscription.status, current_period_start=subscription["items"]["data"][0]["current_period_start"], @@ -384,4 +393,26 @@ class StripeServices: finally: self.db.commit() self.db.close() - \ No newline at end of file + + async def _subscription_expired(self,subscription_id): + try: + subscription = stripe.Subscription.retrieve(subscription_id) + + db_subscription = self.db.query(Subscriptions).filter(Subscriptions.subscription_id == subscription_id).first() + + if not db_subscription: + self.logger.error("Subscription not found!") + raise Exception("Subscription not found!") + + db_subscription.status = subscription.status + self.db.add(db_subscription) + + # TODO: update clinic status + # TODO: send email to user + + return + except Exception as e: + self.logger.error(f"Error ending subscription: {e}") + finally: + self.db.commit() + self.db.close() \ No newline at end of file diff --git a/services/userServices.py b/services/userServices.py index 861a872..e86d9ca 100644 --- a/services/userServices.py +++ b/services/userServices.py @@ -1,3 +1,4 @@ +import asyncio from loguru import logger from sqlalchemy.orm import Session @@ -61,14 +62,13 @@ class UserServices: self.db.add(new_user) 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 + stripe_customer, stripe_account = await asyncio.gather( + self.stripe_service.create_customer( + new_user.id, user.email, user.username + ), + self.stripe_service.create_account( + new_user.id, user.email, user.username, user.mobile + ), ) # Create stripe user