forked from andika/membership-be
Update:- Membership Plan- Donation- Member detail for Member Directory
This commit is contained in:
@@ -122,6 +122,131 @@ def get_subscription_end_date(billing_cycle: str = "yearly") -> datetime:
|
||||
return now + timedelta(days=365)
|
||||
|
||||
|
||||
def calculate_subscription_period(plan, start_date=None, admin_override_dates=None):
|
||||
"""
|
||||
Calculate subscription start and end dates based on plan's custom cycle or billing_cycle.
|
||||
|
||||
Supports three scenarios:
|
||||
1. Plan with custom billing cycle (e.g., Jan 1 - Dec 31 recurring annually)
|
||||
2. Admin-overridden custom dates for manual activation
|
||||
3. Standard relative billing cycle (30/90/365 days from start_date)
|
||||
|
||||
Args:
|
||||
plan: SubscriptionPlan object with custom_cycle fields
|
||||
start_date: Optional custom start date (defaults to now)
|
||||
admin_override_dates: Optional dict with {'start_date': datetime, 'end_date': datetime}
|
||||
|
||||
Returns:
|
||||
tuple: (start_date, end_date) as datetime objects
|
||||
|
||||
Examples:
|
||||
# Plan with Jan 1 - Dec 31 custom cycle, subscribing on May 15, 2025
|
||||
>>> calculate_subscription_period(plan)
|
||||
(datetime(2025, 5, 15), datetime(2025, 12, 31))
|
||||
|
||||
# Plan with Jul 1 - Jun 30 fiscal year cycle, subscribing on Aug 20, 2025
|
||||
>>> calculate_subscription_period(plan)
|
||||
(datetime(2025, 8, 20), datetime(2026, 6, 30))
|
||||
|
||||
# Admin override for custom dates
|
||||
>>> calculate_subscription_period(plan, admin_override_dates={'start_date': ..., 'end_date': ...})
|
||||
(custom_start, custom_end)
|
||||
"""
|
||||
# Admin override takes precedence
|
||||
if admin_override_dates:
|
||||
return (admin_override_dates['start_date'], admin_override_dates['end_date'])
|
||||
|
||||
# Default start date to now if not provided
|
||||
if start_date is None:
|
||||
start_date = datetime.now(timezone.utc)
|
||||
|
||||
# Check if plan uses custom billing cycle
|
||||
if plan.custom_cycle_enabled and plan.custom_cycle_start_month and plan.custom_cycle_start_day:
|
||||
# Calculate end date based on recurring date range
|
||||
current_year = start_date.year
|
||||
|
||||
# Create end date for current cycle
|
||||
try:
|
||||
# Check if this is a year-spanning cycle (e.g., Jul 1 - Jun 30)
|
||||
year_spanning = plan.custom_cycle_end_month < plan.custom_cycle_start_month
|
||||
|
||||
if year_spanning:
|
||||
# Fiscal year scenario: determine if we're in current or next fiscal year
|
||||
cycle_start_this_year = datetime(current_year, plan.custom_cycle_start_month,
|
||||
plan.custom_cycle_start_day, tzinfo=timezone.utc)
|
||||
|
||||
if start_date >= cycle_start_this_year:
|
||||
# We're after the start of the current fiscal year
|
||||
end_date = datetime(current_year + 1, plan.custom_cycle_end_month,
|
||||
plan.custom_cycle_end_day, 23, 59, 59, tzinfo=timezone.utc)
|
||||
else:
|
||||
# We're before the start, so we're in the previous fiscal year
|
||||
end_date = datetime(current_year, plan.custom_cycle_end_month,
|
||||
plan.custom_cycle_end_day, 23, 59, 59, tzinfo=timezone.utc)
|
||||
else:
|
||||
# Calendar-aligned cycle (e.g., Jan 1 - Dec 31)
|
||||
end_date = datetime(current_year, plan.custom_cycle_end_month,
|
||||
plan.custom_cycle_end_day, 23, 59, 59, tzinfo=timezone.utc)
|
||||
|
||||
# If end date has already passed this year, use next year's end date
|
||||
if end_date < start_date:
|
||||
end_date = datetime(current_year + 1, plan.custom_cycle_end_month,
|
||||
plan.custom_cycle_end_day, 23, 59, 59, tzinfo=timezone.utc)
|
||||
|
||||
return (start_date, end_date)
|
||||
|
||||
except ValueError:
|
||||
# Invalid date (e.g., Feb 30) - fall back to relative billing
|
||||
pass
|
||||
|
||||
# Fall back to relative billing cycle
|
||||
if plan.billing_cycle == "yearly":
|
||||
end_date = start_date + timedelta(days=365)
|
||||
elif plan.billing_cycle == "quarterly":
|
||||
end_date = start_date + timedelta(days=90)
|
||||
elif plan.billing_cycle == "monthly":
|
||||
end_date = start_date + timedelta(days=30)
|
||||
elif plan.billing_cycle == "lifetime":
|
||||
# Lifetime membership: set end date 100 years in the future
|
||||
end_date = start_date + timedelta(days=365 * 100)
|
||||
else:
|
||||
# Default to yearly
|
||||
end_date = start_date + timedelta(days=365)
|
||||
|
||||
return (start_date, end_date)
|
||||
|
||||
|
||||
def get_stripe_interval(billing_cycle: str) -> str:
|
||||
"""
|
||||
Map billing_cycle to Stripe recurring interval.
|
||||
|
||||
Args:
|
||||
billing_cycle: Plan billing cycle (yearly, monthly, quarterly, lifetime, custom)
|
||||
|
||||
Returns:
|
||||
str: Stripe interval ("year", "month", or None for one-time)
|
||||
|
||||
Examples:
|
||||
>>> get_stripe_interval("yearly")
|
||||
"year"
|
||||
>>> get_stripe_interval("monthly")
|
||||
"month"
|
||||
>>> get_stripe_interval("quarterly")
|
||||
"month" # Will use interval_count=3
|
||||
>>> get_stripe_interval("lifetime")
|
||||
None # One-time payment
|
||||
"""
|
||||
if billing_cycle in ["yearly", "custom"]:
|
||||
return "year"
|
||||
elif billing_cycle in ["monthly", "quarterly"]:
|
||||
return "month"
|
||||
elif billing_cycle == "lifetime":
|
||||
return None # One-time payment, not recurring
|
||||
else:
|
||||
# Default to year
|
||||
return "year"
|
||||
|
||||
|
||||
def create_stripe_price(
|
||||
product_name: str,
|
||||
price_cents: int,
|
||||
|
||||
Reference in New Issue
Block a user