update
This commit is contained in:
843
Backend/src/payments/routes/payment_routes.py
Normal file
843
Backend/src/payments/routes/payment_routes.py
Normal file
@@ -0,0 +1,843 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query, Request, Header
|
||||
from sqlalchemy.orm import Session, joinedload, selectinload, load_only
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
import os
|
||||
from ...shared.config.database import get_db
|
||||
from ...shared.config.settings import settings
|
||||
from ...shared.config.logging_config import get_logger
|
||||
from ...security.middleware.auth import get_current_user, authorize_roles
|
||||
from ...auth.models.user import User
|
||||
from ..models.payment import Payment, PaymentMethod, PaymentType, PaymentStatus
|
||||
from ...bookings.models.booking import Booking, BookingStatus
|
||||
from ...shared.utils.role_helpers import can_access_all_payments
|
||||
from ...shared.utils.currency_helpers import get_currency_symbol
|
||||
from ...shared.utils.response_helpers import success_response
|
||||
from ...shared.utils.mailer import send_email
|
||||
from ...shared.utils.email_templates import payment_confirmation_email_template, booking_status_changed_email_template
|
||||
from ..services.stripe_service import StripeService
|
||||
from ..services.paypal_service import PayPalService
|
||||
from ..services.borica_service import BoricaService
|
||||
from ...loyalty.services.loyalty_service import LoyaltyService
|
||||
from ...analytics.services.audit_service import audit_service
|
||||
from ..schemas.payment import CreatePaymentRequest, UpdatePaymentStatusRequest, CreateStripePaymentIntentRequest
|
||||
|
||||
logger = get_logger(__name__)
|
||||
router = APIRouter(prefix='/payments', tags=['payments'])
|
||||
|
||||
async def cancel_booking_on_payment_failure(booking: Booking, db: Session, reason: str='Payment failed or canceled'):
|
||||
if booking.status == BookingStatus.cancelled:
|
||||
return
|
||||
from sqlalchemy.orm import selectinload
|
||||
# Use load_only to exclude non-existent columns (rate_plan_id, group_booking_id)
|
||||
booking = db.query(Booking).options(
|
||||
load_only(
|
||||
Booking.id, Booking.booking_number, Booking.user_id, Booking.room_id,
|
||||
Booking.check_in_date, Booking.check_out_date, Booking.num_guests,
|
||||
Booking.total_price, Booking.original_price, Booking.discount_amount,
|
||||
Booking.promotion_code, Booking.status, Booking.deposit_paid,
|
||||
Booking.requires_deposit, Booking.special_requests,
|
||||
Booking.created_at, Booking.updated_at
|
||||
),
|
||||
selectinload(Booking.payments)
|
||||
).filter(Booking.id == booking.id).first()
|
||||
if booking.payments:
|
||||
for payment in booking.payments:
|
||||
if payment.payment_status == PaymentStatus.pending:
|
||||
payment.payment_status = PaymentStatus.failed
|
||||
existing_notes = payment.notes or ''
|
||||
cancellation_note = f'\nPayment cancelled due to booking cancellation: {reason} on {datetime.utcnow().isoformat()}'
|
||||
payment.notes = existing_notes + cancellation_note if existing_notes else cancellation_note.strip()
|
||||
booking.status = BookingStatus.cancelled
|
||||
db.commit()
|
||||
db.refresh(booking)
|
||||
try:
|
||||
from ...system.models.system_settings import SystemSettings
|
||||
client_url_setting = db.query(SystemSettings).filter(SystemSettings.key == 'client_url').first()
|
||||
client_url = client_url_setting.value if client_url_setting and client_url_setting.value else settings.CLIENT_URL or os.getenv('CLIENT_URL', 'http://localhost:5173')
|
||||
if booking.user:
|
||||
email_html = booking_status_changed_email_template(booking_number=booking.booking_number, guest_name=booking.user.full_name if booking.user else 'Guest', status='cancelled', client_url=client_url)
|
||||
await send_email(to=booking.user.email, subject=f'Booking Cancelled - {booking.booking_number}', html=email_html)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Failed to send cancellation email: {e}')
|
||||
|
||||
@router.get('/')
|
||||
async def get_payments(booking_id: Optional[int]=Query(None), status_filter: Optional[str]=Query(None, alias='status'), page: int=Query(1, ge=1), limit: int=Query(10, ge=1, le=100), current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
try:
|
||||
if booking_id:
|
||||
query = db.query(Payment).filter(Payment.booking_id == booking_id)
|
||||
else:
|
||||
query = db.query(Payment)
|
||||
if status_filter:
|
||||
try:
|
||||
query = query.filter(Payment.payment_status == PaymentStatus(status_filter))
|
||||
except ValueError:
|
||||
pass
|
||||
if not can_access_all_payments(current_user, db):
|
||||
query = query.join(Booking).filter(Booking.user_id == current_user.id)
|
||||
total = query.count()
|
||||
# Use load_only to exclude non-existent columns (rate_plan_id, group_booking_id)
|
||||
query = query.options(
|
||||
selectinload(Payment.booking).options(
|
||||
load_only(
|
||||
Booking.id, Booking.booking_number, Booking.user_id, Booking.room_id,
|
||||
Booking.check_in_date, Booking.check_out_date, Booking.num_guests,
|
||||
Booking.total_price, Booking.original_price, Booking.discount_amount,
|
||||
Booking.promotion_code, Booking.status, Booking.deposit_paid,
|
||||
Booking.requires_deposit, Booking.special_requests,
|
||||
Booking.created_at, Booking.updated_at
|
||||
),
|
||||
joinedload(Booking.user)
|
||||
)
|
||||
)
|
||||
offset = (page - 1) * limit
|
||||
payments = query.order_by(Payment.created_at.desc()).offset(offset).limit(limit).all()
|
||||
result = []
|
||||
for payment in payments:
|
||||
payment_dict = {'id': payment.id, 'booking_id': payment.booking_id, 'amount': float(payment.amount) if payment.amount else 0.0, 'payment_method': payment.payment_method.value if isinstance(payment.payment_method, PaymentMethod) else payment.payment_method, 'payment_type': payment.payment_type.value if isinstance(payment.payment_type, PaymentType) else payment.payment_type, 'deposit_percentage': payment.deposit_percentage, 'related_payment_id': payment.related_payment_id, 'payment_status': payment.payment_status.value if isinstance(payment.payment_status, PaymentStatus) else payment.payment_status, 'transaction_id': payment.transaction_id, 'payment_date': payment.payment_date.isoformat() if payment.payment_date else None, 'notes': payment.notes, 'created_at': payment.created_at.isoformat() if payment.created_at else None}
|
||||
if payment.booking:
|
||||
payment_dict['booking'] = {'id': payment.booking.id, 'booking_number': payment.booking.booking_number}
|
||||
if payment.booking.user:
|
||||
payment_dict['booking']['user'] = {'id': payment.booking.user.id, 'name': payment.booking.user.full_name, 'full_name': payment.booking.user.full_name, 'email': payment.booking.user.email}
|
||||
result.append(payment_dict)
|
||||
return success_response(data={'payments': result, 'pagination': {'total': total, 'page': page, 'limit': limit, 'totalPages': (total + limit - 1) // limit}})
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Error fetching payments: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f'Error fetching payments: {str(e)}')
|
||||
|
||||
@router.get('/booking/{booking_id}')
|
||||
async def get_payments_by_booking_id(booking_id: int, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
try:
|
||||
from ...shared.utils.role_helpers import is_admin
|
||||
booking = db.query(Booking).filter(Booking.id == booking_id).first()
|
||||
if not booking:
|
||||
raise HTTPException(status_code=404, detail='Booking not found')
|
||||
if not is_admin(current_user, db) and booking.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='Forbidden')
|
||||
# Use load_only to exclude non-existent columns (rate_plan_id, group_booking_id)
|
||||
payments = db.query(Payment).options(
|
||||
joinedload(Payment.booking).options(
|
||||
load_only(
|
||||
Booking.id, Booking.booking_number, Booking.user_id, Booking.room_id,
|
||||
Booking.check_in_date, Booking.check_out_date, Booking.num_guests,
|
||||
Booking.total_price, Booking.original_price, Booking.discount_amount,
|
||||
Booking.promotion_code, Booking.status, Booking.deposit_paid,
|
||||
Booking.requires_deposit, Booking.special_requests,
|
||||
Booking.created_at, Booking.updated_at
|
||||
),
|
||||
joinedload(Booking.user)
|
||||
)
|
||||
).filter(Payment.booking_id == booking_id).order_by(Payment.created_at.desc()).all()
|
||||
result = []
|
||||
for payment in payments:
|
||||
payment_dict = {'id': payment.id, 'booking_id': payment.booking_id, 'amount': float(payment.amount) if payment.amount else 0.0, 'payment_method': payment.payment_method.value if isinstance(payment.payment_method, PaymentMethod) else payment.payment_method, 'payment_type': payment.payment_type.value if isinstance(payment.payment_type, PaymentType) else payment.payment_type, 'deposit_percentage': payment.deposit_percentage, 'related_payment_id': payment.related_payment_id, 'payment_status': payment.payment_status.value if isinstance(payment.payment_status, PaymentStatus) else payment.payment_status, 'transaction_id': payment.transaction_id, 'payment_date': payment.payment_date.isoformat() if payment.payment_date else None, 'notes': payment.notes, 'created_at': payment.created_at.isoformat() if payment.created_at else None}
|
||||
if payment.booking:
|
||||
payment_dict['booking'] = {'id': payment.booking.id, 'booking_number': payment.booking.booking_number}
|
||||
if payment.booking.user:
|
||||
payment_dict['booking']['user'] = {'id': payment.booking.user.id, 'name': payment.booking.user.full_name, 'full_name': payment.booking.user.full_name, 'email': payment.booking.user.email}
|
||||
result.append(payment_dict)
|
||||
return success_response(data={'payments': result})
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Error in get_payments_by_booking_id: {str(e)}', extra={'booking_id': booking_id}, exc_info=True)
|
||||
raise HTTPException(status_code=500, detail='An error occurred while fetching payments')
|
||||
|
||||
@router.get('/{id}')
|
||||
async def get_payment_by_id(id: int, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
try:
|
||||
payment = db.query(Payment).filter(Payment.id == id).first()
|
||||
if not payment:
|
||||
raise HTTPException(status_code=404, detail='Payment not found')
|
||||
if not can_access_all_payments(current_user, db):
|
||||
if payment.booking and payment.booking.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='Forbidden')
|
||||
payment_dict = {'id': payment.id, 'booking_id': payment.booking_id, 'amount': float(payment.amount) if payment.amount else 0.0, 'payment_method': payment.payment_method.value if isinstance(payment.payment_method, PaymentMethod) else payment.payment_method, 'payment_type': payment.payment_type.value if isinstance(payment.payment_type, PaymentType) else payment.payment_type, 'deposit_percentage': payment.deposit_percentage, 'related_payment_id': payment.related_payment_id, 'payment_status': payment.payment_status.value if isinstance(payment.payment_status, PaymentStatus) else payment.payment_status, 'transaction_id': payment.transaction_id, 'payment_date': payment.payment_date.isoformat() if payment.payment_date else None, 'notes': payment.notes, 'created_at': payment.created_at.isoformat() if payment.created_at else None}
|
||||
if payment.booking:
|
||||
payment_dict['booking'] = {'id': payment.booking.id, 'booking_number': payment.booking.booking_number}
|
||||
return success_response(data={'payment': payment_dict})
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Error in get_payment_by_id: {str(e)}', extra={'payment_id': id}, exc_info=True)
|
||||
raise HTTPException(status_code=500, detail='An error occurred while fetching payment')
|
||||
|
||||
@router.post('/')
|
||||
async def create_payment(
|
||||
request: Request,
|
||||
payment_data: CreatePaymentRequest,
|
||||
current_user: User=Depends(get_current_user),
|
||||
db: Session=Depends(get_db)
|
||||
):
|
||||
"""Create a payment with validated input using Pydantic schema."""
|
||||
client_ip = request.client.host if request.client else None
|
||||
user_agent = request.headers.get('User-Agent')
|
||||
request_id = getattr(request.state, 'request_id', None)
|
||||
|
||||
try:
|
||||
booking_id = payment_data.booking_id
|
||||
amount = payment_data.amount
|
||||
payment_method = payment_data.payment_method
|
||||
payment_type = payment_data.payment_type
|
||||
mark_as_paid = payment_data.mark_as_paid
|
||||
notes = payment_data.notes
|
||||
|
||||
booking = db.query(Booking).filter(Booking.id == booking_id).first()
|
||||
if not booking:
|
||||
raise HTTPException(status_code=404, detail='Booking not found')
|
||||
from ...shared.utils.role_helpers import is_admin
|
||||
if not is_admin(current_user, db) and booking.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='Forbidden')
|
||||
payment = Payment(booking_id=booking_id, amount=amount, payment_method=PaymentMethod(payment_method), payment_type=PaymentType(payment_type), payment_status=PaymentStatus.pending, payment_date=datetime.utcnow() if mark_as_paid else None, notes=notes)
|
||||
if mark_as_paid:
|
||||
payment.payment_status = PaymentStatus.completed
|
||||
payment.payment_date = datetime.utcnow()
|
||||
db.add(payment)
|
||||
db.commit()
|
||||
db.refresh(payment)
|
||||
|
||||
# Send payment receipt notification
|
||||
if payment.payment_status == PaymentStatus.completed:
|
||||
try:
|
||||
from ...notifications.services.notification_service import NotificationService
|
||||
NotificationService.send_payment_receipt(db, payment)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f'Failed to send payment receipt notification: {e}')
|
||||
|
||||
# Award loyalty points if payment completed and booking is confirmed
|
||||
if payment.payment_status == PaymentStatus.completed and booking:
|
||||
try:
|
||||
db.refresh(booking)
|
||||
if booking.status == BookingStatus.confirmed and booking.user:
|
||||
# Check if booking already earned points
|
||||
from ...loyalty.models.loyalty_point_transaction import LoyaltyPointTransaction, TransactionSource
|
||||
existing_points = db.query(LoyaltyPointTransaction).filter(
|
||||
LoyaltyPointTransaction.booking_id == booking.id,
|
||||
LoyaltyPointTransaction.source == TransactionSource.booking
|
||||
).first()
|
||||
|
||||
if not existing_points:
|
||||
# Award points based on payment amount
|
||||
total_paid = sum(float(p.amount) for p in booking.payments if p.payment_status == PaymentStatus.completed)
|
||||
if total_paid > 0:
|
||||
LoyaltyService.earn_points_from_booking(db, booking.user_id, booking, total_paid)
|
||||
except Exception as loyalty_error:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Failed to award loyalty points: {loyalty_error}')
|
||||
|
||||
if payment.payment_status == PaymentStatus.completed and booking.user:
|
||||
try:
|
||||
from ...system.models.system_settings import SystemSettings
|
||||
client_url_setting = db.query(SystemSettings).filter(SystemSettings.key == 'client_url').first()
|
||||
client_url = client_url_setting.value if client_url_setting and client_url_setting.value else settings.CLIENT_URL or os.getenv('CLIENT_URL', 'http://localhost:5173')
|
||||
currency_setting = db.query(SystemSettings).filter(SystemSettings.key == 'platform_currency').first()
|
||||
currency = currency_setting.value if currency_setting and currency_setting.value else 'USD'
|
||||
currency_symbol = get_currency_symbol(currency)
|
||||
email_html = payment_confirmation_email_template(booking_number=booking.booking_number, guest_name=booking.user.full_name, amount=float(payment.amount), payment_method=payment.payment_method.value if isinstance(payment.payment_method, PaymentMethod) else str(payment.payment_method), transaction_id=payment.transaction_id, payment_type=payment.payment_type.value if payment.payment_type else None, total_price=float(booking.total_price), client_url=client_url, currency_symbol=currency_symbol)
|
||||
await send_email(to=booking.user.email, subject=f'Payment Confirmed - {booking.booking_number}', html=email_html)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Failed to send payment confirmation email: {e}')
|
||||
|
||||
# Log payment transaction
|
||||
await audit_service.log_action(
|
||||
db=db,
|
||||
action='payment_created',
|
||||
resource_type='payment',
|
||||
user_id=current_user.id,
|
||||
resource_id=payment.id,
|
||||
ip_address=client_ip,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
details={
|
||||
'booking_id': booking_id,
|
||||
'amount': float(amount),
|
||||
'payment_method': payment_method,
|
||||
'payment_type': payment_type,
|
||||
'payment_status': payment.payment_status.value if hasattr(payment.payment_status, 'value') else str(payment.payment_status),
|
||||
'transaction_id': payment.transaction_id
|
||||
},
|
||||
status='success'
|
||||
)
|
||||
|
||||
return success_response(data={'payment': payment}, message='Payment created successfully')
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
# Log failed payment creation
|
||||
await audit_service.log_action(
|
||||
db=db,
|
||||
action='payment_creation_failed',
|
||||
resource_type='payment',
|
||||
user_id=current_user.id if current_user else None,
|
||||
ip_address=client_ip,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
details={'booking_id': payment_data.booking_id, 'amount': payment_data.amount},
|
||||
status='failed',
|
||||
error_message=str(e)
|
||||
)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.put('/{id}/status', dependencies=[Depends(authorize_roles('admin', 'staff', 'accountant'))])
|
||||
async def update_payment_status(
|
||||
request: Request,
|
||||
id: int,
|
||||
status_data: UpdatePaymentStatusRequest,
|
||||
current_user: User=Depends(authorize_roles('admin', 'staff', 'accountant')),
|
||||
db: Session=Depends(get_db)
|
||||
):
|
||||
"""Update payment status with validated input using Pydantic schema."""
|
||||
client_ip = request.client.host if request.client else None
|
||||
user_agent = request.headers.get('User-Agent')
|
||||
request_id = getattr(request.state, 'request_id', None)
|
||||
|
||||
try:
|
||||
payment = db.query(Payment).filter(Payment.id == id).first()
|
||||
if not payment:
|
||||
raise HTTPException(status_code=404, detail='Payment not found')
|
||||
status_value = status_data.status
|
||||
notes = status_data.notes
|
||||
old_status = payment.payment_status
|
||||
if status_value:
|
||||
try:
|
||||
new_status = PaymentStatus(status_value)
|
||||
payment.payment_status = new_status
|
||||
# Only cancel booking if it's a full refund or all payments are failed
|
||||
if new_status in [PaymentStatus.failed, PaymentStatus.refunded]:
|
||||
# Use load_only to exclude non-existent columns (rate_plan_id, group_booking_id)
|
||||
booking = db.query(Booking).options(
|
||||
load_only(
|
||||
Booking.id, Booking.booking_number, Booking.user_id, Booking.room_id,
|
||||
Booking.check_in_date, Booking.check_out_date, Booking.num_guests,
|
||||
Booking.total_price, Booking.original_price, Booking.discount_amount,
|
||||
Booking.promotion_code, Booking.status, Booking.deposit_paid,
|
||||
Booking.requires_deposit, Booking.special_requests,
|
||||
Booking.created_at, Booking.updated_at
|
||||
),
|
||||
selectinload(Booking.payments)
|
||||
).filter(Booking.id == payment.booking_id).first()
|
||||
if booking and booking.status != BookingStatus.cancelled:
|
||||
# Check if this is a full refund or if all payments are failed
|
||||
total_paid = sum(float(p.amount) for p in booking.payments if p.payment_status == PaymentStatus.completed)
|
||||
total_price = float(booking.total_price) if booking.total_price else 0.0
|
||||
all_payments_failed = all(p.payment_status in [PaymentStatus.failed, PaymentStatus.refunded] for p in booking.payments)
|
||||
is_full_refund = new_status == PaymentStatus.refunded and float(payment.amount) >= total_price
|
||||
|
||||
# Only cancel if it's a full refund or all payments failed
|
||||
if is_full_refund or (new_status == PaymentStatus.failed and all_payments_failed):
|
||||
await cancel_booking_on_payment_failure(booking, db, reason=f'Payment {new_status.value}')
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail='Invalid payment status')
|
||||
|
||||
# Update notes if provided
|
||||
if notes:
|
||||
existing_notes = payment.notes or ''
|
||||
payment.notes = f'{existing_notes}\n{notes}'.strip() if existing_notes else notes
|
||||
db.commit()
|
||||
db.refresh(payment)
|
||||
|
||||
# Log payment status update (admin action)
|
||||
await audit_service.log_action(
|
||||
db=db,
|
||||
action='payment_status_updated',
|
||||
resource_type='payment',
|
||||
user_id=current_user.id,
|
||||
resource_id=payment.id,
|
||||
ip_address=client_ip,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
details={
|
||||
'payment_id': id,
|
||||
'old_status': old_status.value if hasattr(old_status, 'value') else str(old_status),
|
||||
'new_status': payment.payment_status.value if hasattr(payment.payment_status, 'value') else str(payment.payment_status),
|
||||
'booking_id': payment.booking_id,
|
||||
'amount': float(payment.amount) if payment.amount else 0.0,
|
||||
'notes': notes
|
||||
},
|
||||
status='success'
|
||||
)
|
||||
|
||||
if payment.payment_status == PaymentStatus.completed and old_status != PaymentStatus.completed:
|
||||
# Send payment receipt notification
|
||||
try:
|
||||
from ...notifications.services.notification_service import NotificationService
|
||||
NotificationService.send_payment_receipt(db, payment)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f'Failed to send payment receipt notification: {e}')
|
||||
|
||||
try:
|
||||
from ...system.models.system_settings import SystemSettings
|
||||
client_url_setting = db.query(SystemSettings).filter(SystemSettings.key == 'client_url').first()
|
||||
client_url = client_url_setting.value if client_url_setting and client_url_setting.value else settings.CLIENT_URL or os.getenv('CLIENT_URL', 'http://localhost:5173')
|
||||
currency_setting = db.query(SystemSettings).filter(SystemSettings.key == 'platform_currency').first()
|
||||
currency = currency_setting.value if currency_setting and currency_setting.value else 'USD'
|
||||
currency_symbol = get_currency_symbol(currency)
|
||||
payment = db.query(Payment).filter(Payment.id == id).first()
|
||||
if payment.booking and payment.booking.user:
|
||||
client_url_setting = db.query(SystemSettings).filter(SystemSettings.key == 'client_url').first()
|
||||
client_url = client_url_setting.value if client_url_setting and client_url_setting.value else settings.CLIENT_URL or os.getenv('CLIENT_URL', 'http://localhost:5173')
|
||||
currency_setting = db.query(SystemSettings).filter(SystemSettings.key == 'platform_currency').first()
|
||||
currency = currency_setting.value if currency_setting and currency_setting.value else 'USD'
|
||||
currency_symbol = get_currency_symbol(currency)
|
||||
email_html = payment_confirmation_email_template(booking_number=payment.booking.booking_number, guest_name=payment.booking.user.full_name, amount=float(payment.amount), payment_method=payment.payment_method.value if isinstance(payment.payment_method, PaymentMethod) else str(payment.payment_method), transaction_id=payment.transaction_id, client_url=client_url, currency_symbol=currency_symbol)
|
||||
await send_email(to=payment.booking.user.email, subject=f'Payment Confirmed - {payment.booking.booking_number}', html=email_html)
|
||||
if payment.payment_type == PaymentType.deposit and payment.booking:
|
||||
payment.booking.deposit_paid = True
|
||||
if payment.booking.status in [BookingStatus.pending, BookingStatus.cancelled]:
|
||||
payment.booking.status = BookingStatus.confirmed
|
||||
db.commit()
|
||||
elif payment.payment_type == PaymentType.full and payment.booking:
|
||||
total_paid = sum((float(p.amount) for p in payment.booking.payments if p.payment_status == PaymentStatus.completed))
|
||||
if total_paid >= float(payment.booking.total_price):
|
||||
if payment.booking.status in [BookingStatus.pending, BookingStatus.cancelled]:
|
||||
payment.booking.status = BookingStatus.confirmed
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
logger.error(f'Failed to send payment confirmation email: {str(e)}', exc_info=True, extra={'payment_id': payment.id if hasattr(payment, 'id') else None})
|
||||
return success_response(data={'payment': payment}, message='Payment status updated successfully')
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post('/stripe/create-intent')
|
||||
async def create_stripe_payment_intent(intent_data: CreateStripePaymentIntentRequest, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
try:
|
||||
from ..services.stripe_service import get_stripe_secret_key
|
||||
secret_key = get_stripe_secret_key(db)
|
||||
if not secret_key:
|
||||
secret_key = settings.STRIPE_SECRET_KEY
|
||||
if not secret_key:
|
||||
raise HTTPException(status_code=500, detail='Stripe is not configured. Please configure Stripe settings in Admin Panel or set STRIPE_SECRET_KEY environment variable.')
|
||||
booking_id = intent_data.booking_id
|
||||
amount = intent_data.amount
|
||||
currency = intent_data.currency or 'usd'
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info(f'Creating Stripe payment intent - Booking ID: {booking_id}, Amount: ${amount:,.2f}, Currency: {currency}')
|
||||
if amount > 999999.99:
|
||||
logger.error(f"Amount ${amount:,.2f} exceeds Stripe's maximum of $999,999.99")
|
||||
raise HTTPException(status_code=400, detail=f"Amount ${amount:,.2f} exceeds Stripe's maximum of $999,999.99. Please contact support for large payments.")
|
||||
booking = db.query(Booking).filter(Booking.id == booking_id).first()
|
||||
if not booking:
|
||||
raise HTTPException(status_code=404, detail='Booking not found')
|
||||
from ...shared.utils.role_helpers import is_admin
|
||||
if not is_admin(current_user, db) and booking.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='Forbidden')
|
||||
if booking.requires_deposit and (not booking.deposit_paid):
|
||||
deposit_payment = db.query(Payment).filter(Payment.booking_id == booking_id, Payment.payment_type == PaymentType.deposit, Payment.payment_status == PaymentStatus.pending).order_by(Payment.created_at.desc()).first()
|
||||
if deposit_payment:
|
||||
expected_deposit_amount = float(deposit_payment.amount)
|
||||
if abs(amount - expected_deposit_amount) > 0.01:
|
||||
logger.warning(f'Amount mismatch for deposit payment: Requested ${amount:,.2f}, Expected deposit ${expected_deposit_amount:,.2f}, Booking total ${float(booking.total_price):,.2f}')
|
||||
raise HTTPException(status_code=400, detail=f'For pay-on-arrival bookings, only the deposit amount (${expected_deposit_amount:,.2f}) should be charged, not the full booking amount (${float(booking.total_price):,.2f}).')
|
||||
intent = StripeService.create_payment_intent(amount=amount, currency=currency, metadata={'booking_id': str(booking_id), 'booking_number': booking.booking_number, 'user_id': str(current_user.id)}, db=db)
|
||||
from ..services.stripe_service import get_stripe_publishable_key
|
||||
publishable_key = get_stripe_publishable_key(db)
|
||||
if not publishable_key:
|
||||
publishable_key = settings.STRIPE_PUBLISHABLE_KEY
|
||||
if not publishable_key:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning('Stripe publishable key is not configured')
|
||||
raise HTTPException(status_code=500, detail='Stripe publishable key is not configured. Please configure it in Admin Panel (Settings > Stripe Settings) or set STRIPE_PUBLISHABLE_KEY environment variable.')
|
||||
if not intent.get('client_secret'):
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error('Payment intent created but client_secret is missing')
|
||||
raise HTTPException(status_code=500, detail='Failed to create payment intent. Client secret is missing.')
|
||||
return success_response(data={'client_secret': intent['client_secret'], 'payment_intent_id': intent['id'], 'publishable_key': publishable_key}, message='Payment intent created successfully')
|
||||
except HTTPException:
|
||||
raise
|
||||
except ValueError as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Payment intent creation error: {str(e)}')
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Unexpected error creating payment intent: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post('/stripe/confirm')
|
||||
async def confirm_stripe_payment(payment_data: dict, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
try:
|
||||
payment_intent_id = payment_data.get('payment_intent_id')
|
||||
booking_id = payment_data.get('booking_id')
|
||||
if not payment_intent_id:
|
||||
raise HTTPException(status_code=400, detail='payment_intent_id is required')
|
||||
payment = await StripeService.confirm_payment(payment_intent_id=payment_intent_id, db=db, booking_id=booking_id)
|
||||
try:
|
||||
db.commit()
|
||||
except Exception:
|
||||
pass
|
||||
booking = db.query(Booking).filter(Booking.id == payment['booking_id']).first()
|
||||
if booking:
|
||||
db.refresh(booking)
|
||||
if booking and booking.user:
|
||||
try:
|
||||
from ...system.models.system_settings import SystemSettings
|
||||
client_url_setting = db.query(SystemSettings).filter(SystemSettings.key == 'client_url').first()
|
||||
client_url = client_url_setting.value if client_url_setting and client_url_setting.value else settings.CLIENT_URL or os.getenv('CLIENT_URL', 'http://localhost:5173')
|
||||
currency_setting = db.query(SystemSettings).filter(SystemSettings.key == 'platform_currency').first()
|
||||
currency = currency_setting.value if currency_setting and currency_setting.value else 'USD'
|
||||
currency_symbol = get_currency_symbol(currency)
|
||||
email_html = payment_confirmation_email_template(booking_number=booking.booking_number, guest_name=booking.user.full_name, amount=payment['amount'], payment_method='stripe', transaction_id=payment['transaction_id'], payment_type=payment.get('payment_type'), total_price=float(booking.total_price), client_url=client_url, currency_symbol=currency_symbol)
|
||||
await send_email(to=booking.user.email, subject=f'Payment Confirmed - {booking.booking_number}', html=email_html)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f'Failed to send payment confirmation email: {e}')
|
||||
return success_response(data={'payment': payment, 'booking': {'id': booking.id if booking else None, 'booking_number': booking.booking_number if booking else None, 'status': booking.status.value if booking else None}}, message='Payment confirmed successfully')
|
||||
except HTTPException:
|
||||
db.rollback()
|
||||
raise
|
||||
except ValueError as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Payment confirmation error: {str(e)}')
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Unexpected error confirming payment: {str(e)}', exc_info=True)
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post('/stripe/webhook')
|
||||
async def stripe_webhook(request: Request, db: Session=Depends(get_db)):
|
||||
try:
|
||||
from ..services.stripe_service import get_stripe_webhook_secret
|
||||
webhook_secret = get_stripe_webhook_secret(db)
|
||||
if not webhook_secret:
|
||||
webhook_secret = settings.STRIPE_WEBHOOK_SECRET
|
||||
if not webhook_secret:
|
||||
raise HTTPException(status_code=503, detail={'status': 'error', 'message': 'Stripe webhook secret is not configured. Please configure it in Admin Panel (Settings > Stripe Settings) or set STRIPE_WEBHOOK_SECRET environment variable.'})
|
||||
payload = await request.body()
|
||||
signature = request.headers.get('stripe-signature')
|
||||
if not signature:
|
||||
raise HTTPException(status_code=400, detail='Missing stripe-signature header')
|
||||
result = await StripeService.handle_webhook(payload=payload, signature=signature, db=db)
|
||||
return success_response(data=result)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post('/paypal/create-order')
|
||||
async def create_paypal_order(order_data: dict, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
try:
|
||||
from ..services.paypal_service import get_paypal_client_id, get_paypal_client_secret
|
||||
client_id = get_paypal_client_id(db)
|
||||
if not client_id:
|
||||
client_id = settings.PAYPAL_CLIENT_ID
|
||||
client_secret = get_paypal_client_secret(db)
|
||||
if not client_secret:
|
||||
client_secret = settings.PAYPAL_CLIENT_SECRET
|
||||
if not client_id or not client_secret:
|
||||
raise HTTPException(status_code=500, detail='PayPal is not configured. Please configure PayPal settings in Admin Panel or set PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET environment variables.')
|
||||
booking_id = order_data.get('booking_id')
|
||||
amount = float(order_data.get('amount', 0))
|
||||
currency = order_data.get('currency', 'USD')
|
||||
if not booking_id or amount <= 0:
|
||||
raise HTTPException(status_code=400, detail='booking_id and amount are required')
|
||||
if amount > 100000:
|
||||
raise HTTPException(status_code=400, detail=f"Amount ${amount:,.2f} exceeds PayPal's maximum of $100,000. Please contact support for large payments.")
|
||||
from ...shared.utils.role_helpers import is_admin
|
||||
booking = db.query(Booking).filter(Booking.id == booking_id).first()
|
||||
if not booking:
|
||||
raise HTTPException(status_code=404, detail='Booking not found')
|
||||
if not is_admin(current_user, db) and booking.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='Forbidden')
|
||||
if booking.requires_deposit and (not booking.deposit_paid):
|
||||
deposit_payment = db.query(Payment).filter(Payment.booking_id == booking_id, Payment.payment_type == PaymentType.deposit, Payment.payment_status == PaymentStatus.pending).order_by(Payment.created_at.desc()).first()
|
||||
if deposit_payment:
|
||||
expected_deposit_amount = float(deposit_payment.amount)
|
||||
if abs(amount - expected_deposit_amount) > 0.01:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f'Amount mismatch for deposit payment: Requested ${amount:,.2f}, Expected deposit ${expected_deposit_amount:,.2f}, Booking total ${float(booking.total_price):,.2f}')
|
||||
raise HTTPException(status_code=400, detail=f'For pay-on-arrival bookings, only the deposit amount (${expected_deposit_amount:,.2f}) should be charged, not the full booking amount (${float(booking.total_price):,.2f}).')
|
||||
client_url = settings.CLIENT_URL or os.getenv('CLIENT_URL', 'http://localhost:5173')
|
||||
return_url = order_data.get('return_url', f'{client_url}/payment/paypal/return')
|
||||
cancel_url = order_data.get('cancel_url', f'{client_url}/payment/paypal/cancel')
|
||||
order = PayPalService.create_order(amount=amount, currency=currency, metadata={'booking_id': str(booking_id), 'booking_number': booking.booking_number, 'user_id': str(current_user.id), 'description': f'Hotel Booking Payment - {booking.booking_number}', 'return_url': return_url, 'cancel_url': cancel_url}, db=db)
|
||||
if not order.get('approval_url'):
|
||||
raise HTTPException(status_code=500, detail='Failed to create PayPal order. Approval URL is missing.')
|
||||
return success_response(data={'order_id': order['id'], 'approval_url': order['approval_url'], 'status': order['status']}, message='PayPal order created successfully')
|
||||
except HTTPException:
|
||||
raise
|
||||
except ValueError as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'PayPal order creation error: {str(e)}')
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Unexpected error creating PayPal order: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post('/paypal/cancel')
|
||||
async def cancel_paypal_payment(payment_data: dict, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
try:
|
||||
booking_id = payment_data.get('booking_id')
|
||||
if not booking_id:
|
||||
raise HTTPException(status_code=400, detail='booking_id is required')
|
||||
payment = db.query(Payment).filter(Payment.booking_id == booking_id, Payment.payment_method == PaymentMethod.paypal, Payment.payment_status == PaymentStatus.pending).order_by(Payment.created_at.desc()).first()
|
||||
if not payment:
|
||||
payment = db.query(Payment).filter(Payment.booking_id == booking_id, Payment.payment_type == PaymentType.deposit, Payment.payment_status == PaymentStatus.pending).order_by(Payment.created_at.desc()).first()
|
||||
if payment:
|
||||
payment.payment_status = PaymentStatus.failed
|
||||
db.commit()
|
||||
db.refresh(payment)
|
||||
booking = db.query(Booking).filter(Booking.id == booking_id).first()
|
||||
if booking and booking.status != BookingStatus.cancelled:
|
||||
await cancel_booking_on_payment_failure(booking, db, reason='PayPal payment canceled by user')
|
||||
return success_response(message='Payment canceled and booking cancelled')
|
||||
except HTTPException:
|
||||
db.rollback()
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post('/paypal/capture')
|
||||
async def capture_paypal_payment(payment_data: dict, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
try:
|
||||
order_id = payment_data.get('order_id')
|
||||
booking_id = payment_data.get('booking_id')
|
||||
if not order_id:
|
||||
raise HTTPException(status_code=400, detail='order_id is required')
|
||||
payment = await PayPalService.confirm_payment(order_id=order_id, db=db, booking_id=booking_id)
|
||||
try:
|
||||
db.commit()
|
||||
except Exception:
|
||||
pass
|
||||
booking = db.query(Booking).filter(Booking.id == payment['booking_id']).first()
|
||||
if booking:
|
||||
db.refresh(booking)
|
||||
if booking and booking.user:
|
||||
try:
|
||||
from ...system.models.system_settings import SystemSettings
|
||||
client_url_setting = db.query(SystemSettings).filter(SystemSettings.key == 'client_url').first()
|
||||
client_url = client_url_setting.value if client_url_setting and client_url_setting.value else settings.CLIENT_URL or os.getenv('CLIENT_URL', 'http://localhost:5173')
|
||||
currency_setting = db.query(SystemSettings).filter(SystemSettings.key == 'platform_currency').first()
|
||||
currency = currency_setting.value if currency_setting and currency_setting.value else 'USD'
|
||||
currency_symbol = get_currency_symbol(currency)
|
||||
email_html = payment_confirmation_email_template(booking_number=booking.booking_number, guest_name=booking.user.full_name, amount=payment['amount'], payment_method='paypal', transaction_id=payment['transaction_id'], payment_type=payment.get('payment_type'), total_price=float(booking.total_price), client_url=client_url, currency_symbol=currency_symbol)
|
||||
await send_email(to=booking.user.email, subject=f'Payment Confirmed - {booking.booking_number}', html=email_html)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f'Failed to send payment confirmation email: {e}')
|
||||
return success_response(data={'payment': payment, 'booking': {'id': booking.id if booking else None, 'booking_number': booking.booking_number if booking else None, 'status': booking.status.value if booking else None}}, message='Payment confirmed successfully')
|
||||
except HTTPException:
|
||||
db.rollback()
|
||||
raise
|
||||
except ValueError as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'PayPal payment confirmation error: {str(e)}')
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Unexpected error confirming PayPal payment: {str(e)}', exc_info=True)
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post('/borica/create-payment')
|
||||
async def create_borica_payment(payment_data: dict, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
try:
|
||||
from ..services.borica_service import get_borica_terminal_id, get_borica_merchant_id
|
||||
terminal_id = get_borica_terminal_id(db)
|
||||
merchant_id = get_borica_merchant_id(db)
|
||||
if not terminal_id or not merchant_id:
|
||||
if not settings.BORICA_TERMINAL_ID or not settings.BORICA_MERCHANT_ID:
|
||||
raise HTTPException(status_code=500, detail='Borica is not configured. Please configure Borica settings in Admin Panel or set BORICA_TERMINAL_ID and BORICA_MERCHANT_ID environment variables.')
|
||||
booking_id = payment_data.get('booking_id')
|
||||
amount = float(payment_data.get('amount', 0))
|
||||
currency = payment_data.get('currency', 'BGN')
|
||||
if not booking_id or amount <= 0:
|
||||
raise HTTPException(status_code=400, detail='booking_id and amount are required')
|
||||
if amount > 100000:
|
||||
raise HTTPException(status_code=400, detail=f"Amount {amount:,.2f} exceeds maximum of 100,000. Please contact support for large payments.")
|
||||
from ...shared.utils.role_helpers import is_admin
|
||||
booking = db.query(Booking).filter(Booking.id == booking_id).first()
|
||||
if not booking:
|
||||
raise HTTPException(status_code=404, detail='Booking not found')
|
||||
if not is_admin(current_user, db) and booking.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='Forbidden')
|
||||
if booking.requires_deposit and (not booking.deposit_paid):
|
||||
deposit_payment = db.query(Payment).filter(Payment.booking_id == booking_id, Payment.payment_type == PaymentType.deposit, Payment.payment_status == PaymentStatus.pending).order_by(Payment.created_at.desc()).first()
|
||||
if deposit_payment:
|
||||
expected_deposit_amount = float(deposit_payment.amount)
|
||||
if abs(amount - expected_deposit_amount) > 0.01:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f'Amount mismatch for deposit payment: Requested {amount:,.2f}, Expected deposit {expected_deposit_amount:,.2f}, Booking total {float(booking.total_price):,.2f}')
|
||||
raise HTTPException(status_code=400, detail=f'For pay-on-arrival bookings, only the deposit amount ({expected_deposit_amount:,.2f}) should be charged, not the full booking amount ({float(booking.total_price):,.2f}).')
|
||||
transaction_id = BoricaService.generate_transaction_id(booking_id)
|
||||
client_url = settings.CLIENT_URL or os.getenv('CLIENT_URL', 'http://localhost:5173')
|
||||
return_url = payment_data.get('return_url', f'{client_url}/payment/borica/return')
|
||||
description = f'Hotel Booking Payment - {booking.booking_number}'
|
||||
payment_request = BoricaService.create_payment_request(amount=amount, currency=currency, order_id=transaction_id, description=description, return_url=return_url, db=db)
|
||||
payment_type = PaymentType.full
|
||||
if booking.requires_deposit and (not booking.deposit_paid):
|
||||
payment_type = PaymentType.deposit
|
||||
payment = Payment(booking_id=booking_id, amount=amount, payment_method=PaymentMethod.borica, payment_type=payment_type, payment_status=PaymentStatus.pending, transaction_id=transaction_id, notes=f'Borica payment initiated - Order: {transaction_id}')
|
||||
db.add(payment)
|
||||
db.commit()
|
||||
db.refresh(payment)
|
||||
return success_response(data={'payment_request': payment_request, 'payment_id': payment.id, 'transaction_id': transaction_id}, message='Borica payment request created successfully')
|
||||
except HTTPException:
|
||||
raise
|
||||
except ValueError as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Borica payment creation error: {str(e)}')
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Unexpected error creating Borica payment: {str(e)}', exc_info=True)
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post('/borica/callback')
|
||||
async def borica_callback(request: Request, db: Session=Depends(get_db)):
|
||||
"""
|
||||
Handle Borica payment callback (POST from Borica gateway).
|
||||
Borica sends POST data with payment response.
|
||||
"""
|
||||
try:
|
||||
form_data = await request.form()
|
||||
response_data = dict(form_data)
|
||||
|
||||
# Also try to get from JSON if available
|
||||
try:
|
||||
json_data = await request.json()
|
||||
response_data.update(json_data)
|
||||
except:
|
||||
pass
|
||||
|
||||
payment = await BoricaService.confirm_payment(response_data=response_data, db=db)
|
||||
try:
|
||||
db.commit()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
booking = db.query(Booking).filter(Booking.id == payment['booking_id']).first()
|
||||
if booking:
|
||||
db.refresh(booking)
|
||||
|
||||
if booking and booking.user:
|
||||
try:
|
||||
from ...system.models.system_settings import SystemSettings
|
||||
client_url_setting = db.query(SystemSettings).filter(SystemSettings.key == 'client_url').first()
|
||||
client_url = client_url_setting.value if client_url_setting and client_url_setting.value else settings.CLIENT_URL or os.getenv('CLIENT_URL', 'http://localhost:5173')
|
||||
currency_setting = db.query(SystemSettings).filter(SystemSettings.key == 'platform_currency').first()
|
||||
currency = currency_setting.value if currency_setting and currency_setting.value else 'USD'
|
||||
currency_symbol = get_currency_symbol(currency)
|
||||
email_html = payment_confirmation_email_template(booking_number=booking.booking_number, guest_name=booking.user.full_name, amount=payment['amount'], payment_method='borica', transaction_id=payment['transaction_id'], payment_type=payment.get('payment_type'), total_price=float(booking.total_price), client_url=client_url, currency_symbol=currency_symbol)
|
||||
await send_email(to=booking.user.email, subject=f'Payment Confirmed - {booking.booking_number}', html=email_html)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f'Failed to send payment confirmation email: {e}')
|
||||
|
||||
# Redirect to return URL with success status
|
||||
return_url = response_data.get('BACKREF', '')
|
||||
if return_url:
|
||||
from fastapi.responses import RedirectResponse
|
||||
return RedirectResponse(url=f"{return_url}?status=success&order={response_data.get('ORDER', '')}&bookingId={payment['booking_id']}")
|
||||
|
||||
return success_response(data={'payment': payment, 'booking': {'id': booking.id if booking else None, 'booking_number': booking.booking_number if booking else None, 'status': booking.status.value if booking else None}}, message='Payment confirmed successfully')
|
||||
except HTTPException:
|
||||
db.rollback()
|
||||
raise
|
||||
except ValueError as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Borica payment callback error: {str(e)}')
|
||||
db.rollback()
|
||||
# Redirect to return URL with error status
|
||||
return_url = dict(await request.form()).get('BACKREF', '') if hasattr(request, 'form') else ''
|
||||
if return_url:
|
||||
from fastapi.responses import RedirectResponse
|
||||
return RedirectResponse(url=f"{return_url}?status=error&error={str(e)}")
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Unexpected error in Borica callback: {str(e)}', exc_info=True)
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post('/borica/confirm')
|
||||
async def confirm_borica_payment(response_data: dict, db: Session=Depends(get_db)):
|
||||
try:
|
||||
payment = await BoricaService.confirm_payment(response_data=response_data, db=db)
|
||||
try:
|
||||
db.commit()
|
||||
except Exception:
|
||||
pass
|
||||
booking = db.query(Booking).filter(Booking.id == payment['booking_id']).first()
|
||||
if booking:
|
||||
db.refresh(booking)
|
||||
if booking and booking.user:
|
||||
try:
|
||||
from ...system.models.system_settings import SystemSettings
|
||||
client_url_setting = db.query(SystemSettings).filter(SystemSettings.key == 'client_url').first()
|
||||
client_url = client_url_setting.value if client_url_setting and client_url_setting.value else settings.CLIENT_URL or os.getenv('CLIENT_URL', 'http://localhost:5173')
|
||||
currency_setting = db.query(SystemSettings).filter(SystemSettings.key == 'platform_currency').first()
|
||||
currency = currency_setting.value if currency_setting and currency_setting.value else 'USD'
|
||||
currency_symbol = get_currency_symbol(currency)
|
||||
email_html = payment_confirmation_email_template(booking_number=booking.booking_number, guest_name=booking.user.full_name, amount=payment['amount'], payment_method='borica', transaction_id=payment['transaction_id'], payment_type=payment.get('payment_type'), total_price=float(booking.total_price), client_url=client_url, currency_symbol=currency_symbol)
|
||||
await send_email(to=booking.user.email, subject=f'Payment Confirmed - {booking.booking_number}', html=email_html)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f'Failed to send payment confirmation email: {e}')
|
||||
return success_response(data={'payment': payment, 'booking': {'id': booking.id if booking else None, 'booking_number': booking.booking_number if booking else None, 'status': booking.status.value if booking else None}}, message='Payment confirmed successfully')
|
||||
except HTTPException:
|
||||
db.rollback()
|
||||
raise
|
||||
except ValueError as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Borica payment confirmation error: {str(e)}')
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f'Unexpected error confirming Borica payment: {str(e)}', exc_info=True)
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Reference in New Issue
Block a user