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

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__) 

8 

9class PaymentService: 

10 BASE_PRICE_PER_HOUR = Decimal('30.00') 

11 

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 

16 

17 total = price_per_hour * Decimal(str(duration_hours)) 

18 

19 return total.quantize(Decimal('0.01')) 

20 

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 

26 

27 logger.info( 

28 f"Initiating Stripe PaymentIntent for booking {booking.id}: " 

29 f"{amount_uah} UAH ({amount_cents} {currency})" 

30 ) 

31 

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 ) 

39 

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 } 

56 

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 } 

65 

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 

70 

71class BookingNotificationService: 

72 @staticmethod 

73 def send_booking_confirmation(booking): 

74 logger.info(f"Sending booking confirmation for {booking.id} to {booking.user.email}") 

75 

76 

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 

82 

83 logger.info(f"Sending cancellation confirmation for {booking.id} to {booking.user.email}") 

84 

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}") 

89 

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}" 

94 

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