feat: subscription ended webhook

refactor: subscription table
This commit is contained in:
deepvasoya 2025-06-03 19:19:17 +05:30
parent f545a5b75b
commit 7f2f730426
4 changed files with 104 additions and 27 deletions

View File

@ -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 ###

View File

@ -5,9 +5,13 @@ from .CustomBase import CustomBase
class Subscriptions(Base, CustomBase): class Subscriptions(Base, CustomBase):
__tablename__ = "subscriptions" __tablename__ = "subscriptions"
id = Column(Integer, primary_key=True, index=True) 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) 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) subscription_id = Column(String,index=True)
clinic_id = Column(Integer, index=True) clinic_id = Column(Integer, index=True)
status = Column(String) status = Column(String)

View File

@ -35,7 +35,7 @@ class StripeServices:
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:
customer = stripe.Customer.create( customer = await stripe.Customer.create_async(
email=email, name=name, metadata={"user_id": user_id} email=email, name=name, metadata={"user_id": user_id}
) )
return customer return customer
@ -45,14 +45,14 @@ class StripeServices:
async def delete_customer(self, customer_id: str): async def delete_customer(self, customer_id: str):
try: try:
stripe.Customer.delete(customer_id) await stripe.Customer.delete_async(customer_id)
except stripe.error.StripeError as e: except stripe.error.StripeError as e:
self.logger.error(f"Error deleting customer: {e}") self.logger.error(f"Error deleting customer: {e}")
raise raise
async def create_account(self, user_id: int, email: str, name: str, phone: str): async def create_account(self, user_id: int, email: str, name: str, phone: str):
try: try:
account = stripe.Account.create( account = await stripe.Account.create_async(
type="express", type="express",
country="AU", country="AU",
capabilities={ capabilities={
@ -70,7 +70,7 @@ class StripeServices:
async def delete_account(self, account_id: str): async def delete_account(self, account_id: str):
try: try:
stripe.Account.delete(account_id) await stripe.Account.delete_async(account_id)
except stripe.error.StripeError as e: except stripe.error.StripeError as e:
self.logger.error(f"Error deleting account: {e}") self.logger.error(f"Error deleting account: {e}")
raise raise
@ -96,9 +96,9 @@ class StripeServices:
if not subscription: if not subscription:
raise ResourceNotFoundException("Subscription not found!") 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"] stripe_subscription["latest_invoice"]
) )
return invoice.hosted_invoice_url return invoice.hosted_invoice_url
@ -154,7 +154,7 @@ class StripeServices:
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 = await stripe.checkout.Session.create_async(
payment_method_types=["card"], payment_method_types=["card"],
line_items=[ line_items=[
{ {
@ -182,7 +182,7 @@ class StripeServices:
async def create_setup_fees(self, customer_id: str, amount: int): async def create_setup_fees(self, customer_id: str, amount: int):
try: try:
setup_intent = stripe.InvoiceItem.create( setup_intent = await stripe.InvoiceItem.create_async(
customer=customer_id, customer=customer_id,
amount=amount, amount=amount,
currency="aud", 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( payment_log = PaymentLogs(
customer_id=customer_id, customer_id=customer_id,
@ -294,8 +294,11 @@ class StripeServices:
) )
self.logger.info(f"Stripe webhook event type: {event['type']}") self.logger.info(f"Stripe webhook event type: {event['type']}")
if event["type"] == "checkout.session.expired": if event["type"] == "customer.subscription.deleted":
pass 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": if event["type"] == "checkout.session.completed":
unique_clinic_id = event["data"]["object"]["metadata"]["unique_clinic_id"] unique_clinic_id = event["data"]["object"]["metadata"]["unique_clinic_id"]
@ -307,9 +310,9 @@ class StripeServices:
session_id = event["data"]["object"]["id"] session_id = event["data"]["object"]["id"]
subscription_id = event["data"]["object"]["subscription"] 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, "clinic_id": clinic_id,
"customer_id": customer_id, "customer_id": customer_id,
"account_id": account_id, "account_id": account_id,
@ -326,7 +329,7 @@ class StripeServices:
finally: finally:
self.db.close() 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: try:
self.db.query(PaymentSessions).filter(PaymentSessions.clinic_id == clinic_id).delete() self.db.query(PaymentSessions).filter(PaymentSessions.clinic_id == clinic_id).delete()
@ -353,16 +356,22 @@ class StripeServices:
self.db.commit() self.db.commit()
self.db.close() self.db.close()
def _create_subscription_entry(self,data:dict): async def _create_subscription_entry(self,data:dict):
try: try:
subscription = stripe.Subscription.retrieve(data["subscription_id"]) 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( new_subscription = Subscriptions(
clinic_id=data["clinic_id"], clinic_id=data["clinic_id"],
customer_id=data["customer_id"], customer_id=data["customer_id"],
account_id=data["account_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"], subscription_id=data["subscription_id"],
status=subscription.status, status=subscription.status,
current_period_start=subscription["items"]["data"][0]["current_period_start"], current_period_start=subscription["items"]["data"][0]["current_period_start"],
@ -384,4 +393,26 @@ class StripeServices:
finally: finally:
self.db.commit() self.db.commit()
self.db.close() self.db.close()
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()

View File

@ -1,3 +1,4 @@
import asyncio
from loguru import logger from loguru import logger
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@ -61,14 +62,13 @@ class UserServices:
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, stripe_account = await asyncio.gather(
stripe_customer = await self.stripe_service.create_customer( self.stripe_service.create_customer(
new_user.id, user.email, user.username new_user.id, user.email, user.username
) ),
self.stripe_service.create_account(
# Create stripe account new_user.id, user.email, user.username, user.mobile
stripe_account = await self.stripe_service.create_account( ),
new_user.id, user.email, user.username, user.mobile
) )
# Create stripe user # Create stripe user