"""Billing API routes for AnchorHost.""" from currency_service import calculate_stripe_amount, get_cached_exchange_rates from database_service import database_connection, dict_from_row from email_service import get_email_service from flask import Blueprint, jsonify, request from runtime_config import ( CACHE_DURATION, EXCHANGE_RATE_CACHE, STRIPE_CURRENCY_MAP, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, logger, ) from server_billing import get_user_monthly_fees from server_capacity import calculate_monthly_server_cost from server_records import get_user_servers import stripe from user_service import add_credit_transaction, authenticate_user, get_bearer_token, get_user_by_id, update_user_credits __all__ = ['create_checkout_session', 'estimate_monthly_cost', 'get_billing_history', 'get_exchange_rates_api', 'get_session_status', 'payment_cancelled', 'payment_success', 'stripe_webhook'] stripe.api_key = STRIPE_SECRET_KEY bp = Blueprint("billing", __name__) @bp.route('/api/user/billing/history', methods=['GET']) def get_billing_history(): """Get user's billing history""" try: token = get_bearer_token() user = authenticate_user(token) if not user: return jsonify({'success': False, 'error': 'Unauthorized'}), 401 limit = request.args.get('limit', 10, type=int) # Pobierz opłaty miesięczne monthly_fees = get_user_monthly_fees(user['id'], limit) # Pobierz informacje o serwerach i datach rozliczeniowych servers = get_user_servers(user['id']) server_billing_info = [] for server in servers: with database_connection() as conn: cursor = conn.cursor() cursor.execute(''' SELECT * FROM server_billing WHERE server_id = ? ''', (server['id'],)) billing = dict_from_row(cursor.fetchone()) if billing: server['billing_info'] = billing server_billing_info.append(server) return jsonify({ 'success': True, 'monthly_fees': monthly_fees, 'active_servers': server_billing_info, 'total_active_servers': len([s for s in servers if s['status'] in ['Running', 'Starting']]) }), 200 except Exception as e: logger.error(f"Error getting billing history: {e}") return jsonify({'success': False, 'error': str(e)}), 500 @bp.route('/api/user/billing/estimate', methods=['POST']) def estimate_monthly_cost(): """Estimate monthly cost for a server configuration""" try: token = get_bearer_token() user = authenticate_user(token) if not user: return jsonify({'success': False, 'error': 'Unauthorized'}), 401 data = request.get_json() if not data: return jsonify({'success': False, 'error': 'No configuration provided'}), 400 # Utwórz mock serwer do kalkulacji mock_server = { 'cpu': data.get('cpu', '1 vCPU'), 'ram': data.get('ram', '1GB'), 'storage': data.get('storage', '10GB'), 'type': data.get('serverType', 'vps'), 'name': 'Estimation' } monthly_cost = calculate_monthly_server_cost(mock_server) return jsonify({ 'success': True, 'monthly_cost_credits': monthly_cost, 'monthly_cost_euro': monthly_cost / 100, # 100 kredytów = 1 euro 'estimated_daily_cost': monthly_cost / 30, # Dzienny koszt 'currency': 'EUR', 'pricing_model': { 'cpu_per_vcpu': '0.25 EUR', 'ram_per_gb': '0.25 EUR', 'storage_per_gb': '0.05 EUR', 'server_type_multipliers': { 'vps': '1.0x', 'game': '1.5x', 'website': '1.2x', 'database': '1.3x' } } }), 200 except Exception as e: logger.error(f"Error estimating monthly cost: {e}") return jsonify({'success': False, 'error': str(e)}), 500 @bp.route('/api/stripe/create-checkout-session', methods=['POST']) def create_checkout_session(): """Create a Stripe checkout session""" try: token = get_bearer_token() user = authenticate_user(token) if not user: return jsonify({'error': 'Unauthorized'}), 401 data = request.get_json() credits = data.get('credits', 1000) currency = data.get('currency', 'USD').upper() if credits < 100: return jsonify({'error': 'Minimum purchase is 100 credits'}), 400 if currency not in STRIPE_CURRENCY_MAP: return jsonify({'error': f'Currency {currency} not supported'}), 400 # Calculate amount in smallest currency unit amount = calculate_stripe_amount(credits, currency) stripe_currency = STRIPE_CURRENCY_MAP[currency] # Get currency symbol for display currency_data = next((c for c in [ {'code': 'USD', 'symbol': '$'}, {'code': 'EUR', 'symbol': '€'}, {'code': 'GBP', 'symbol': '£'}, {'code': 'PLN', 'symbol': 'zł'}, {'code': 'PHP', 'symbol': '₱'}, {'code': 'CZK', 'symbol': 'Kč'} ] if c['code'] == currency), {'code': currency, 'symbol': currency}) # Create Stripe checkout session session = stripe.checkout.Session.create( payment_method_types=[ 'card', 'paypal', 'blik', 'revolut_pay', 'ideal', 'bancontact', 'eps', 'twint', 'link', 'multibanco', 'klarna' ], line_items=[{ 'price_data': { 'currency': stripe_currency, 'product_data': { 'name': f'{credits} AnchorHost Credits', 'description': f'Purchase {credits} credits for your AnchorHost account', 'images': ['https://your-logo-url.com/logo.png'], # Optional: Add your logo }, 'unit_amount': amount, }, 'quantity': 1, }], mode='payment', success_url=request.host_url + f'payment-success?session_id={{CHECKOUT_SESSION_ID}}', cancel_url=request.host_url + 'payment-cancelled', metadata={ 'user_id': user['id'], 'username': user['username'], 'credits': str(credits), 'currency': currency }, customer_email=user['email'], ) logger.info(f"Created Stripe checkout session for user {user['username']}: {credits} credits, {currency}") return jsonify({ 'success': True, 'sessionId': session.id, 'url': session.url }), 200 except stripe.error.StripeError as e: logger.error(f"Stripe error: {e}") return jsonify({'error': f'Payment service error: {str(e)}'}), 500 except Exception as e: logger.error(f"Error creating checkout session: {e}") return jsonify({'error': str(e)}), 500 @bp.route('/api/stripe/webhook', methods=['POST']) def stripe_webhook(): """Handle Stripe webhook events""" payload = request.get_data() sig_header = request.headers.get('Stripe-Signature') try: event = stripe.Webhook.construct_event( payload, sig_header, STRIPE_WEBHOOK_SECRET ) except ValueError as e: logger.error(f"Invalid payload: {e}") return jsonify({'error': 'Invalid payload'}), 400 except stripe.error.SignatureVerificationError as e: logger.error(f"Invalid signature: {e}") return jsonify({'error': 'Invalid signature'}), 400 # Handle the event if event['type'] == 'checkout.session.completed': session = event['data']['object'] # Get metadata user_id = session['metadata'].get('user_id') username = session['metadata'].get('username') credits = int(session['metadata'].get('credits', 0)) currency = session['metadata'].get('currency', 'USD') logger.info(f"Payment completed: {username} purchased {credits} credits for {session['amount_total']/100} {currency}") # Add credits to user account try: user = get_user_by_id(user_id) if user: new_credits = user.get('credits', 0) + credits if update_user_credits(user_id, new_credits): add_credit_transaction( user_id, credits, 'purchase', f'Stripe payment: {credits} credits purchased for {session["amount_total"]/100} {currency}' ) logger.info(f"✅ Credits added to {username}: +{credits} (new balance: {new_credits})") # Send confirmation email email_svc = get_email_service() if email_svc: try: email_svc.send_payment_confirmation( user['email'], username, credits, session['amount_total'] / 100, currency ) except Exception as e: logger.error(f"Failed to send confirmation email: {e}") else: logger.error(f"Failed to update credits for {username}") else: logger.error(f"User not found: {user_id}") except Exception as e: logger.error(f"Error processing payment for {username}: {e}") elif event['type'] == 'checkout.session.async_payment_succeeded': # Handle async payment success (e.g., bank transfers) session = event['data']['object'] logger.info(f"Async payment succeeded: {session['id']}") elif event['type'] == 'checkout.session.async_payment_failed': # Handle async payment failure session = event['data']['object'] logger.warning(f"Async payment failed: {session['id']}") return jsonify({'success': True}), 200 @bp.route('/api/stripe/session-status/', methods=['GET']) def get_session_status(session_id): """Get the status of a Stripe checkout session""" try: token = get_bearer_token() user = authenticate_user(token) if not user: return jsonify({'error': 'Unauthorized'}), 401 session = stripe.checkout.Session.retrieve(session_id) return jsonify({ 'success': True, 'status': session.status, 'payment_status': session.payment_status, 'amount_total': session.amount_total, 'currency': session.currency, 'customer_email': session.customer_email }), 200 except stripe.error.StripeError as e: logger.error(f"Stripe error: {e}") return jsonify({'error': str(e)}), 500 except Exception as e: logger.error(f"Error getting session status: {e}") return jsonify({'error': str(e)}), 500 @bp.route('/payment-success') def payment_success(): """Payment success page""" session_id = request.args.get('session_id') return f''' Payment Successful
✅

Payment Successful!

Your credits have been added to your account. Thank you for your purchase!

This window will close automatically in 5 seconds...

Return to Dashboard
Session ID: {session_id}
''' @bp.route('/payment-cancelled') def payment_cancelled(): """Payment cancelled page""" return ''' Payment Cancelled
❌

Payment Cancelled

Your payment was cancelled. No charges were made to your account.

This window will close automatically in 3 seconds...

Return to Dashboard
''' @bp.route('/api/exchange-rates', methods=['GET']) def get_exchange_rates_api(): """Get current exchange rates""" try: rates = get_cached_exchange_rates() return jsonify({ 'success': True, 'rates': rates, 'base_currency': 'USD', 'last_updated': EXCHANGE_RATE_CACHE.get('last_updated'), 'cache_duration_seconds': CACHE_DURATION }), 200 except Exception as e: logger.error(f"Error getting exchange rates: {e}") return jsonify({ 'success': False, 'error': str(e) }), 500