Design and implement a meal delivery subscription system that enables customers to set up recurring food deliveries. The system must automatically convert active subscriptions into orders when the scheduled delivery date arrives, but only if two critical conditions are met: the customer has sufficient funds in their account wallet, and all requested items are currently available in the warehouse inventory.
Your implementation should handle the complete lifecycle of subscriptions including creation, modification, and automated order generation. The system needs to track user wallet balances, product inventory levels, and subscription schedules to determine when and whether to fulfill each subscription.
Implement a User class that tracks user ID and wallet balance
Implement a Product class that manages product details including ID, name, price, and available stock quantity
Implement a Subscription class that stores subscription details including which products and quantities the user wants delivered, delivery frequency (in days), and the last delivery date
Implement an Order class that represents a fulfilled subscription with order ID, associated subscription, user, list of products, total cost, and creation timestamp
Implement a SubscriptionService class that manages the entire system with methods to:
Ensure atomic transactions: either all products are available and the full order is created, or nothing happens
Track subscription status (active, paused, cancelled)
Example 1: Successful Subscription Processing
` Setup: User: Alice (ID: "user1", Balance: $150) Products: - Milk (ID: "prod1", Price: $5, Stock: 100) - Bread (ID: "prod2", Price: $3, Stock: 50) Subscription: Every 7 days, 2x Milk, 3x Bread
Process: Total cost = (2 × $5) + (3 × $3) = $19 Alice has $150 (sufficient) All products in stock (sufficient)
Result: Order created successfully Alice's new balance: $131 Milk stock: 98 Bread stock: 47 `
Example 2: Insufficient Funds
` Setup: User: Bob (ID: "user2", Balance: $10) Products: - Chicken (ID: "prod3", Price: $12, Stock: 20) Subscription: Every 3 days, 2x Chicken
Process: Total cost = 2 × $12 = $24 Bob has $10 (insufficient)
Result: Order NOT created Balance unchanged: $10 Stock unchanged: 20 Subscription remains active for next cycle `
Example 3: Out of Stock
` Setup: User: Carol (ID: "user3", Balance: $100) Products: - Eggs (ID: "prod4", Price: $4, Stock: 2) - Butter (ID: "prod5", Price: $6, Stock: 10) Subscription: Every 7 days, 5x Eggs, 1x Butter
Process: Total cost = (5 × $4) + (1 × $6) = $26 Carol has $100 (sufficient) Eggs requested: 5, available: 2 (insufficient)
Result: Order NOT created (atomic operation failed) Balance unchanged: $100 Stock unchanged: Eggs 2, Butter 10 `
Hint 1: Class Design Strategy Start by identifying the core entities and their relationships. Users have wallets, Products have inventory, Subscriptions link Users to Products with quantities, and Orders are the result of successful subscription processing. Use composition to build these relationships, and consider using dictionaries or maps to store collections of each entity type for quick lookups by ID.
Hint 2: Validation Logic When processing a subscription, follow a two-phase approach: first validate (check balance and inventory), then execute (deduct balance and stock) only if all validations pass. Calculate the total cost upfront by iterating through all products in the subscription. Check inventory for each product before making any changes. This ensures atomicity of the operation.
Hint 3: Subscription Scheduling Track the last processed date for each subscription. When processing subscriptions, compare the current date with the last processed date plus the frequency. Only process subscriptions where enough time has elapsed. After successful order creation, update the last processed date to the current date.
Full Solution `` Solution Explanation:
This implementation uses object-oriented design principles to create a robust subscription service:
- Class Structure: Four main entity classes (User, Product, Subscription, Order) represent the domain models, and a service class (SubscriptionService) orchestrates the business logic.
- Validation Before Execution: The
_validate_subscriptionmethod performs all checks (balance, inventory) before any state changes occur, ensuring atomic operations.- Subscription Scheduling: The
is_due_for_processingmethod uses datetime comparison to determine if a subscription should be processed based on its frequency and last processing date.- Atomic Transactions: The
_process_single_subscriptionmethod either completes fully (creating order, deducting balance, reducing stock) or rolls back on failure.- State Management: Enums are used for subscription status, and proper state tracking ensures subscriptions are only processed when active and due.
Time Complexity:
- Creating subscription: O(p) where p is number of products
- Processing single subscription: O(p) for validation and order creation
- Processing all subscriptions: O(s × p) where s is number of subscriptions
Space Complexity:
- O(u + p + s + o) where u = users, p = products, s = subscriptions, o = orders
- All entities are stored in dictionaries for O(1) lookup time
from datetime import datetime, timedelta
from typing import Dict, List, Optional
from enum import Enum
class SubscriptionStatus(Enum):
ACTIVE = "active"
PAUSED = "paused"
CANCELLED = "cancelled"
class User:
def __init__(self, user_id: str, wallet_balance: float):
self.user_id = user_id
self.wallet_balance = wallet_balance
def deduct_balance(self, amount: float) -> bool:
"""Deduct amount from wallet if sufficient balance exists"""
if self.wallet_balance >= amount:
self.wallet_balance -= amount
return True
return False
def add_balance(self, amount: float):
"""Add funds to wallet"""
self.wallet_balance += amount
class Product:
def __init__(self, product_id: str, name: str, price: float, stock: int):
self.product_id = product_id
self.name = name
self.price = price
self.stock = stock
def reduce_stock(self, quantity: int) -> bool:
"""Reduce stock if available"""
if self.stock >= quantity:
self.stock -= quantity
return True
return False
def add_stock(self, quantity: int):
"""Add stock to inventory"""
self.stock += quantity
class Subscription:
def __init__(self, subscription_id: str, user_id: str, frequency_days: int):
self.subscription_id = subscription_id
self.user_id = user_id
self.frequency_days = frequency_days
self.products: Dict[str, int] = {} # product_id -> quantity
self.status = SubscriptionStatus.ACTIVE
self.last_processed_date: Optional[datetime] = None
self.created_date = datetime.now()
def add_product(self, product_id: str, quantity: int):
"""Add or update product quantity in subscription"""
self.products[product_id] = quantity
def remove_product(self, product_id: str) -> bool:
"""Remove product from subscription"""
if product_id in self.products:
del self.products[product_id]
return True
return False
def is_due_for_processing(self) -> bool:
"""Check if subscription should be processed based on frequency"""
if self.status != SubscriptionStatus.ACTIVE:
return False
if self.last_processed_date is None:
return True
next_delivery_date = self.last_processed_date + timedelta(days=self.frequency_days)
return datetime.now() >= next_delivery_date
class Order:
def __init__(self, order_id: str, subscription_id: str, user_id: str):
self.order_id = order_id
self.subscription_id = subscription_id
self.user_id = user_id
self.products: Dict[str, int] = {} # product_id -> quantity
self.total_cost = 0.0
self.created_at = datetime.now()
def add_product(self, product_id: str, quantity: int, price: float):
"""Add product to order"""
self.products[product_id] = quantity
self.total_cost += price * quantity
class SubscriptionService:
def __init__(self):
self.users: Dict[str, User] = {}
self.products: Dict[str, Product] = {}
self.subscriptions: Dict[str, Subscription] = {}
self.orders: Dict[str, Order] = {}
self.order_counter = 0
self.subscription_counter = 0
def add_user(self, user: User):
"""Register a user in the system"""
self.users[user.user_id] = user
def add_product(self, product: Product):
"""Register a product in the system"""
self.products[product.product_id] = product
def create_subscription(self, user_id: str, products: Dict[str, int],
frequency_days: int) -> Optional[str]:
"""Create a new subscription for a user"""
if user_id not in self.users:
return None
# Validate all products exist
for product_id in products.keys():
if product_id not in self.products:
return None
self.subscription_counter += 1
subscription_id = f"sub_{self.subscription_counter}"
subscription = Subscription(subscription_id, user_id, frequency_days)
for product_id, quantity in products.items():
subscription.add_product(product_id, quantity)
self.subscriptions[subscription_id] = subscription
return subscription_id
def add_product_to_subscription(self, subscription_id: str,
product_id: str, quantity: int) -> bool:
"""Add or update a product in an existing subscription"""
if subscription_id not in self.subscriptions:
return False
if product_id not in self.products:
return False
subscription = self.subscriptions[subscription_id]
subscription.add_product(product_id, quantity)
return True
def pause_subscription(self, subscription_id: str) -> bool:
"""Pause a subscription"""
if subscription_id in self.subscriptions:
self.subscriptions[subscription_id].status = SubscriptionStatus.PAUSED
return True
return False
def cancel_subscription(self, subscription_id: str) -> bool:
"""Cancel a subscription"""
if subscription_id in self.subscriptions:
self.subscriptions[subscription_id].status = SubscriptionStatus.CANCELLED
return True
return False
def _validate_subscription(self, subscription: Subscription) -> tuple[bool, float]:
"""
Validate if subscription can be processed.
Returns (is_valid, total_cost)
"""
user = self.users.get(subscription.user_id)
if not user:
return False, 0.0
# Calculate total cost and check inventory
total_cost = 0.0
for product_id, quantity in subscription.products.items():
product = self.products.get(product_id)
if not product:
return False, 0.0
# Check if enough stock available
if product.stock < quantity:
return False, 0.0
total_cost += product.price * quantity
# Check if user has sufficient balance
if user.wallet_balance < total_cost:
return False, 0.0
return True, total_cost
def _process_single_subscription(self, subscription: Subscription) -> Optional[str]:
"""
Process a single subscription and create an order if valid.
Returns order_id if successful, None otherwise.
"""
# Validate subscription
is_valid, total_cost = self._validate_subscription(subscription)
if not is_valid:
return None
user = self.users[subscription.user_id]
# Create order
self.order_counter += 1
order_id = f"order_{self.order_counter}"
order = Order(order_id, subscription.subscription_id, subscription.user_id)
# Deduct balance
if not user.deduct_balance(total_cost):
return None
# Reduce inventory and add products to order
for product_id, quantity in subscription.products.items():
product = self.products[product_id]
if not product.reduce_stock(quantity):
# Rollback: this shouldn't happen as we validated, but handle it
user.add_balance(total_cost)
return None
order.add_product(product_id, quantity, product.price)
# Update subscription last processed date
subscription.last_processed_date = datetime.now()
# Store order
self.orders[order_id] = order
return order_id
def process_subscriptions(self) -> Dict[str, List[str]]:
"""
Process all due subscriptions.
Returns dict with 'successful' and 'failed' order lists.
"""
results = {
'successful': [],
'failed': []
}
for subscription in self.subscriptions.values():
if not subscription.is_due_for_processing():
continue
order_id = self._process_single_subscription(subscription)
if order_id:
results['successful'].append(order_id)
else:
results['failed'].append(subscription.subscription_id)
return results
def get_user_orders(self, user_id: str) -> List[Order]:
"""Get all orders for a specific user"""
return [order for order in self.orders.values() if order.user_id == user_id]
def get_subscription_details(self, subscription_id: str) -> Optional[Dict]:
"""Get detailed information about a subscription"""
if subscription_id not in self.subscriptions:
return None
subscription = self.subscriptions[subscription_id]
user = self.users.get(subscription.user_id)
details = {
'subscription_id': subscription.subscription_id,
'user_id': subscription.user_id,
'user_balance': user.wallet_balance if user else 0,
'frequency_days': subscription.frequency_days,
'status': subscription.status.value,
'products': [],
'estimated_cost': 0.0
}
for product_id, quantity in subscription.products.items():
product = self.products.get(product_id)
if product:
cost = product.price * quantity
details['products'].append({
'product_id': product_id,
'name': product.name,
'quantity': quantity,
'unit_price': product.price,
'total': cost,
'in_stock': product.stock >= quantity
})
details['estimated_cost'] += cost
return details