Commit c4e12078 authored by Your Name's avatar Your Name

feat(payments): add subscription API endpoints

- Add subscription methods to PaymentService (create, upgrade, downgrade, cancel, get_status)
- Initialize SubscriptionManager and SubscriptionRenewalProcessor in PaymentService
- Add REST API endpoints for subscription management:
  - POST /api/subscriptions - Create subscription
  - POST /api/subscriptions/upgrade - Upgrade tier
  - POST /api/subscriptions/downgrade - Downgrade tier
  - POST /api/subscriptions/cancel - Cancel subscription
  - GET /api/subscriptions/status - Get subscription status
- Add process_renewals() and process_retries() methods for scheduler integration

Completes Phase 3 Task 6: Subscription API Endpoints
parent 0f1caa1f
...@@ -26,6 +26,23 @@ class PaymentService: ...@@ -26,6 +26,23 @@ class PaymentService:
self.blockchain_monitor = BlockchainMonitor(db_manager, config) self.blockchain_monitor = BlockchainMonitor(db_manager, config)
self.stripe_handler = StripeHandler(db_manager, config) self.stripe_handler = StripeHandler(db_manager, config)
self.paypal_handler = PayPalHandler(db_manager, config) self.paypal_handler = PayPalHandler(db_manager, config)
# Initialize subscription sub-services
from aisbf.payments.subscription.manager import SubscriptionManager
from aisbf.payments.subscription.renewal import SubscriptionRenewalProcessor
self.subscription_manager = SubscriptionManager(
db_manager,
self.stripe_handler,
self.paypal_handler,
self.wallet_manager,
self.price_service
)
self.renewal_processor = SubscriptionRenewalProcessor(
db_manager,
self.subscription_manager
)
async def initialize(self): async def initialize(self):
"""Initialize payment service (run on startup)""" """Initialize payment service (run on startup)"""
...@@ -225,3 +242,57 @@ class PaymentService: ...@@ -225,3 +242,57 @@ class PaymentService:
except Exception as e: except Exception as e:
logger.error(f"Error deleting payment method: {e}") logger.error(f"Error deleting payment method: {e}")
return {'success': False, 'error': str(e)} return {'success': False, 'error': str(e)}
async def create_subscription(self, user_id: int, tier_id: int,
payment_method_id: int,
billing_cycle: str) -> dict:
"""Create new subscription"""
return await self.subscription_manager.create_subscription(
user_id, tier_id, payment_method_id, billing_cycle
)
async def upgrade_subscription(self, user_id: int, new_tier_id: int) -> dict:
"""Upgrade subscription"""
return await self.subscription_manager.upgrade_subscription(user_id, new_tier_id)
async def downgrade_subscription(self, user_id: int, new_tier_id: int) -> dict:
"""Downgrade subscription"""
return await self.subscription_manager.downgrade_subscription(user_id, new_tier_id)
async def cancel_subscription(self, user_id: int) -> dict:
"""Cancel subscription"""
return await self.subscription_manager.cancel_subscription(user_id)
async def get_subscription_status(self, user_id: int) -> dict:
"""Get user's subscription status"""
with self.db._get_connection() as conn:
cursor = conn.cursor()
placeholder = '?' if self.db.db_type == 'sqlite' else '%s'
cursor.execute(f"""
SELECT s.*, t.name as tier_name, t.price_monthly, t.price_yearly,
pm.type as payment_type, pm.gateway
FROM subscriptions s
JOIN tiers t ON s.tier_id = t.id
LEFT JOIN payment_methods pm ON s.payment_method_id = pm.id
WHERE s.user_id = {placeholder}
ORDER BY s.created_at DESC
LIMIT 1
""", (user_id,))
row = cursor.fetchone()
if not row:
return None
# Get column names
columns = [desc[0] for desc in cursor.description]
subscription = dict(zip(columns, row))
return subscription
async def process_renewals(self):
"""Process subscription renewals (called by scheduler)"""
await self.renewal_processor.process_due_renewals()
async def process_retries(self):
"""Process payment retries (called by scheduler)"""
await self.renewal_processor.process_retry_queue()
...@@ -1803,6 +1803,81 @@ async def paypal_webhook(request: Request): ...@@ -1803,6 +1803,81 @@ async def paypal_webhook(request: Request):
return result return result
# Subscription API endpoints
@app.post("/api/subscriptions")
async def create_subscription(request: Request):
"""Create new subscription"""
current_user = await get_current_user(request)
body = await request.json()
result = await payment_service.create_subscription(
current_user['id'],
body['tier_id'],
body['payment_method_id'],
body['billing_cycle']
)
if not result['success']:
raise HTTPException(status_code=400, detail=result['error'])
return result
@app.post("/api/subscriptions/upgrade")
async def upgrade_subscription(request: Request):
"""Upgrade subscription"""
current_user = await get_current_user(request)
body = await request.json()
result = await payment_service.upgrade_subscription(
current_user['id'],
body['tier_id']
)
if not result['success']:
raise HTTPException(status_code=400, detail=result['error'])
return result
@app.post("/api/subscriptions/downgrade")
async def downgrade_subscription(request: Request):
"""Downgrade subscription"""
current_user = await get_current_user(request)
body = await request.json()
result = await payment_service.downgrade_subscription(
current_user['id'],
body['tier_id']
)
if not result['success']:
raise HTTPException(status_code=400, detail=result['error'])
return result
@app.post("/api/subscriptions/cancel")
async def cancel_subscription(request: Request):
"""Cancel subscription"""
current_user = await get_current_user(request)
result = await payment_service.cancel_subscription(current_user['id'])
if not result['success']:
raise HTTPException(status_code=400, detail=result['error'])
return result
@app.get("/api/subscriptions/status")
async def get_subscription_status(request: Request):
"""Get subscription status"""
current_user = await get_current_user(request)
status = await payment_service.get_subscription_status(current_user['id'])
return {'subscription': status}
# Dashboard routes # Dashboard routes
@app.get("/dashboard/analytics", response_class=HTMLResponse) @app.get("/dashboard/analytics", response_class=HTMLResponse)
async def dashboard_analytics( async def dashboard_analytics(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment