Files
CityCards_Customer_Flutter/lib/checkout/bloc/checkOut/checkout_bloc.dart
2026-02-13 15:27:14 +05:30

261 lines
7.9 KiB
Dart

import 'package:flutter_bloc/flutter_bloc.dart';
import '../../repository/all_coupons_repository.dart';
import '../../repository/checkout_repository.dart';
import 'checkout_event.dart';
import 'checkout_state.dart';
class CheckoutBloc extends Bloc<CheckoutEvent, CheckoutState> {
final AllCouponsRepository couponsRepository;
final CheckoutRepository checkoutRepository;
CheckoutBloc({
required this.couponsRepository,
required this.checkoutRepository,
}) : super(CheckoutInitialState()) {
on<FetchCheckoutCouponsEvent>(_onFetchCheckoutCoupons);
on<ApplyCouponEvent>(_onApplyCoupon);
on<RemoveCouponEvent>(_onRemoveCoupon);
on<ApplyCouponToBackendEvent>(_onApplyCouponToBackend); // 🆕 NEW
on<InitiatePaymentEvent>(_onInitiatePayment); // 🆕 NEW
on<ConfirmPaymentEvent>(_onConfirmPayment); // 🆕 NEW
}
Future<void> _onFetchCheckoutCoupons(
FetchCheckoutCouponsEvent event,
Emitter<CheckoutState> emit,
) async {
emit(CheckoutCouponsLoadingState());
try {
final coupons = await couponsRepository.fetchAllCoupons();
emit(CheckoutCouponsLoadedState(coupons: coupons));
} catch (e) {
emit(CheckoutCouponsErrorState(error: e.toString()));
}
}
void _onApplyCoupon(
ApplyCouponEvent event,
Emitter<CheckoutState> emit,
) {
if (state is CheckoutCouponsLoadedState) {
final currentState = state as CheckoutCouponsLoadedState;
emit(currentState.copyWith(appliedCoupon: event.coupon));
}
}
Future<void> _onRemoveCoupon(
RemoveCouponEvent event,
Emitter<CheckoutState> emit,
) async {
if (state is CheckoutCouponsLoadedState) {
final currentState = state as CheckoutCouponsLoadedState;
// Show loading
emit(currentState.copyWith(isApplyingCoupon: true, couponError: null));
try {
// Call API with empty coupon code
await checkoutRepository.applyCoupon(
bookingId: event.bookingId,
couponCode: '', // Empty string to remove coupon
);
// Clear applied coupon from state
emit(currentState.copyWith(
clearAppliedCoupon: true,
isApplyingCoupon: false,
couponError: null,
));
} catch (e) {
emit(currentState.copyWith(
isApplyingCoupon: false,
couponError: e.toString(),
));
}
}
}
/// 🆕 Apply Coupon to Backend
/// Calls the PUT /apply-coupon API
Future<void> _onApplyCouponToBackend(
ApplyCouponToBackendEvent event,
Emitter<CheckoutState> emit,
) async {
if (state is CheckoutCouponsLoadedState) {
final currentState = state as CheckoutCouponsLoadedState;
// Show loading
emit(currentState.copyWith(isApplyingCoupon: true, couponError: null));
try {
// Call API
final response = await checkoutRepository.applyCoupon(
bookingId: event.bookingId,
couponCode: event.couponCode,
);
// Find the coupon from the list
final appliedCoupon = currentState.coupons.firstWhere(
(c) => c.couponCode == event.couponCode,
orElse: () => currentState.coupons.first,
);
// Update state with applied coupon
emit(currentState.copyWith(
appliedCoupon: appliedCoupon,
isApplyingCoupon: false,
couponError: null,
));
// Success message will be handled in view
} catch (e) {
emit(currentState.copyWith(
isApplyingCoupon: false,
couponError: e.toString(),
));
}
}
}
/// 🆕 Initiate Payment
/// Calls the /pay API to get clientSecret for Stripe
Future<void> _onInitiatePayment(
InitiatePaymentEvent event,
Emitter<CheckoutState> emit,
) async {
// Show loading state
if (state is CheckoutCouponsLoadedState) {
final currentState = state as CheckoutCouponsLoadedState;
emit(currentState.copyWith(
isInitiatingPayment: true,
paymentError: null,
clientSecret: null,
));
} else {
emit(CheckoutPaymentInitiatingState());
}
try {
// Call the /pay API
final response = await checkoutRepository.initiatePayment(
bookingId: event.bookingId,
);
// Extract clientSecret and bookingId from response
final clientSecret = response['clientSecret'] as String?;
final bookingId = response['bookingId'] as int?;
// Validate response
if (clientSecret == null || clientSecret.isEmpty) {
emit(CheckoutPaymentInitiationErrorState(
error: 'Payment initialization failed - no client secret received from server',
));
return;
}
if (bookingId == null) {
emit(CheckoutPaymentInitiationErrorState(
error: 'Payment initialization failed - no booking ID received from server',
));
return;
}
// Emit success state with clientSecret
if (state is CheckoutCouponsLoadedState) {
final currentState = state as CheckoutCouponsLoadedState;
emit(currentState.copyWith(
isInitiatingPayment: false,
clientSecret: clientSecret,
bookingId: bookingId,
paymentError: null,
));
} else {
emit(CheckoutPaymentInitiatedState(
clientSecret: clientSecret,
bookingId: bookingId,
));
}
} catch (e) {
if (state is CheckoutCouponsLoadedState) {
final currentState = state as CheckoutCouponsLoadedState;
emit(currentState.copyWith(
isInitiatingPayment: false,
paymentError: e.toString(),
));
} else {
emit(CheckoutPaymentInitiationErrorState(
error: e.toString(),
));
}
}
}
/// 🆕 Confirm Payment
/// Called after Stripe payment succeeds or fails
/// Sends stripeStatus and paymentStatus to backend
Future<void> _onConfirmPayment(
ConfirmPaymentEvent event,
Emitter<CheckoutState> emit,
) async {
// 🔒 GUARD: Prevent duplicate confirmation calls
if (state is CheckoutCouponsLoadedState) {
final currentState = state as CheckoutCouponsLoadedState;
if (currentState.hasConfirmationBeenSent) {
print('⚠️ [CHECKOUT BLOC] Payment confirmation already sent. Ignoring duplicate call.');
return;
}
}
// Show loading state
if (state is CheckoutCouponsLoadedState) {
final currentState = state as CheckoutCouponsLoadedState;
emit(currentState.copyWith(
isConfirmingPayment: true,
confirmationError: null,
isPaymentConfirmed: false,
hasConfirmationBeenSent: true, // 🔒 Mark as sent
));
} else {
emit(CheckoutPaymentConfirmingState());
}
try {
// Call the confirm-payment API
final response = await checkoutRepository.confirmPayment(
bookingId: event.bookingId,
stripeStatus: event.stripeStatus,
paymentStatus: event.paymentStatus,
);
// Emit success state with booking details
if (state is CheckoutCouponsLoadedState) {
final currentState = state as CheckoutCouponsLoadedState;
emit(currentState.copyWith(
isConfirmingPayment: false,
isPaymentConfirmed: true,
confirmationError: null,
bookingDetails: response,
clearClientSecret: true,
));
} else {
emit(CheckoutPaymentConfirmedState(
bookingDetails: response,
));
}
} catch (e) {
if (state is CheckoutCouponsLoadedState) {
final currentState = state as CheckoutCouponsLoadedState;
emit(currentState.copyWith(
isConfirmingPayment: false,
isPaymentConfirmed: false,
confirmationError: e.toString(),
hasConfirmationBeenSent: false, // 🔓 Reset on error to allow retry
));
} else {
emit(CheckoutPaymentConfirmationErrorState(
error: e.toString(),
));
}
}
}
}