1111from paddle_billing import Client #noqa: TCH002
1212from paddle_billing .Entities .Shared import CustomData
1313from paddle_billing .Resources .Customers .Operations import CreateCustomer , UpdateCustomer
14- from paddle_billing .Resources .Subscriptions .Operations import ListSubscriptions
14+ from paddle_billing .Resources .Subscriptions .Operations import ListSubscriptions , CancelSubscription
15+ from paddle_billing .Entities .Subscriptions import SubscriptionEffectiveFrom
1516
1617from langflow .services .auth .clerk_metadata_constants import (
1718 ORGANISATION_CREATED_BY_KEY ,
@@ -75,13 +76,17 @@ async def has_active_subscription(
7576 "subscription_plan_key" : None ,
7677 "paddle_subscription_id" : None ,
7778 "organisation_created_by" : created_by ,
79+ "next_billed_at" : None ,
80+ "current_period_end" : None ,
81+ "cancel_scheduled" : False ,
7882 }
7983
8084 paddle_client = client or get_paddle_client ()
8185 subscription = await asyncio .to_thread (
8286 paddle_client .subscriptions .get ,
8387 sub_id ,
8488 )
89+
8590 status = getattr (subscription , "status" , None )
8691 if status :
8792 status = str (status ).lower ()
@@ -92,13 +97,35 @@ async def has_active_subscription(
9297 plan_key = subscription_custom_data .get ("plan_key" )
9398 if plan_key is not None :
9499 plan_key = str (plan_key )
95- logger .info (f"Subscription { sub_id } - status: { status } , has_access: { has_access } , active_statuses: { ACTIVE_SUBSCRIPTION_STATUSES } " )
100+
101+ next_billed_at = getattr (subscription , "next_billed_at" , None )
102+
103+ current_period = getattr (subscription , "current_billing_period" , None )
104+ current_period_end = None
105+ if current_period :
106+ current_period_end = getattr (current_period , "ends_at" , None )
107+
108+ scheduled_change = getattr (subscription , "scheduled_change" , None )
109+ cancel_scheduled = False
110+ if scheduled_change :
111+ action = getattr (scheduled_change , "action" , None )
112+ cancel_scheduled = str (action ).lower () == "cancel"
113+
114+ logger .info (
115+ f"Subscription { sub_id } - status: { status } , "
116+ f"has_access: { has_access } , next_billed_at: { next_billed_at } , "
117+ f"cancel_scheduled: { cancel_scheduled } "
118+ )
119+
96120 return {
97121 "has_access" : has_access ,
98122 "subscription_status" : status ,
99123 "subscription_plan_key" : plan_key ,
100124 "paddle_subscription_id" : sub_id ,
101125 "organisation_created_by" : created_by ,
126+ "next_billed_at" : next_billed_at ,
127+ "current_period_end" : current_period_end ,
128+ "cancel_scheduled" : cancel_scheduled ,
102129 }
103130
104131
@@ -433,3 +460,54 @@ async def retry_with_backoff(
433460 await asyncio .sleep (delay )
434461
435462 raise last_exception
463+
464+
465+ async def cancel_subscription (
466+ * ,
467+ subscription_id : str ,
468+ effective_from_immediately : bool = False ,
469+ client : Client | None = None ,
470+ ):
471+ """
472+ Cancel a Paddle subscription.
473+
474+ Args:
475+ subscription_id: Paddle subscription ID (sub_xxx)
476+ effective_from_immediately:
477+ True -> cancel immediately
478+ False -> cancel at next billing period (default)
479+ """
480+ paddle_client = client or get_paddle_client ()
481+
482+ # Map boolean -> Paddle enum
483+ effective_from = (
484+ SubscriptionEffectiveFrom .IMMEDIATELY
485+ if effective_from_immediately
486+ else SubscriptionEffectiveFrom .NEXT_BILLING_PERIOD
487+ )
488+
489+ operation = CancelSubscription (
490+ effective_from = effective_from
491+ )
492+
493+ try :
494+ subscription = await asyncio .to_thread (
495+ paddle_client .subscriptions .cancel ,
496+ subscription_id ,
497+ operation ,
498+ )
499+
500+ logger .info (
501+ f"Cancelled subscription { subscription_id } "
502+ f"(effective_from={ effective_from } )"
503+ )
504+
505+ return {
506+ "subscription_id" : subscription_id ,
507+ "status" : getattr (subscription , "status" , None ),
508+ "effective_from" : str (effective_from ),
509+ }
510+
511+ except Exception as e :
512+ logger .exception (f"Error cancelling subscription { subscription_id } : { e } " )
513+ raise
0 commit comments