Coverage for src/api/services.py: 92%
59 statements
« prev ^ index » next coverage.py v7.11.1, created at 2025-11-08 10:41 +0000
« prev ^ index » next coverage.py v7.11.1, created at 2025-11-08 10:41 +0000
1from decimal import Decimal
2from typing import Dict, Any
3import logging
4from django.conf import settings
5import stripe
6stripe.api_key = settings.STRIPE_SECRET_KEY
7logger = logging.getLogger(__name__)
9class PaymentService:
10 BASE_PRICE_PER_HOUR = Decimal('30.00')
12 @staticmethod
13 def calculate_price(booking) -> Decimal:
14 duration_hours = (booking.end_at - booking.start_at).total_seconds() / 3600
15 price_per_hour = PaymentService.BASE_PRICE_PER_HOUR
17 total = price_per_hour * Decimal(str(duration_hours))
19 return total.quantize(Decimal('0.01'))
21 @staticmethod
22 def initiate_payment(booking) -> Dict[str, Any]:
23 amount_uah = PaymentService.calculate_price(booking)
24 amount_cents = int(amount_uah * 100)
25 currency = settings.STRIPE_CURRENCY
27 logger.info(
28 f"Initiating Stripe PaymentIntent for booking {booking.id}: "
29 f"{amount_uah} UAH ({amount_cents} {currency})"
30 )
32 try:
33 intent = stripe.PaymentIntent.create(
34 amount=amount_cents,
35 currency=currency,
36 description=f'Parking spot booking #{booking.spot.number} at {booking.spot.lot.name}',
37 metadata={'booking_id': booking.id, 'user_id': booking.user.id},
38 )
40 return {
41 'status': 'requires_payment_method',
42 'client_secret': intent.client_secret,
43 'payment_intent_id': intent.id,
44 'amount': str(amount_uah),
45 'currency': currency.upper(),
46 'message': 'Stripe Payment Intent created. Use client_secret on frontend.'
47 }
48 except Exception as e:
49 logger.error(f"Stripe Payment Intent creation failed: {e}")
50 return {
51 'status': 'error',
52 'message': f'Stripe API Error: {str(e)}',
53 'amount': str(amount_uah),
54 'currency': currency.upper(),
55 }
57 @staticmethod
58 def process_refund(booking) -> Dict[str, Any]:
59 logger.info(f"Processing refund for booking {booking.id}")
60 return {
61 'status': 'mock',
62 'message': 'Refund will be processed once Stripe integration is fully implemented',
63 'booking_id': booking.id,
64 }
66 @staticmethod
67 def verify_payment(order_id: str, signature: str, data: str) -> bool:
68 logger.info(f"Verifying payment for order {order_id}")
69 return True
71class BookingNotificationService:
72 @staticmethod
73 def send_booking_confirmation(booking):
74 logger.info(f"Sending booking confirmation for {booking.id} to {booking.user.email}")
77 @staticmethod
78 def send_cancellation_confirmation(booking):
79 if booking.user is None:
80 logger.warning(f"Skipping cancellation confirmation for {booking.id}. User field is NULL.")
81 return
83 logger.info(f"Sending cancellation confirmation for {booking.id} to {booking.user.email}")
85 @staticmethod
86 def send_reminder(booking):
87 """Sends a reminder before the booking starts"""
88 logger.info(f"Sending reminder for booking {booking.id}")
90class CancellationService:
91 @staticmethod
92 def get_operator_cancellation_reason(operator_username: str, comment: str) -> str:
93 return f"Cancelled by Operator ({operator_username}): {comment}"
95class SpotUpdateService:
96 @staticmethod
97 def update_spot(spot, validated_data):
98 for field, value in validated_data.items():
99 setattr(spot, field, value)
100 spot.save()
101 return spot