feat: Phase 1 critical gaps — gamification API, Razorpay checkout, Google OAuth, notifications
- Fix gamification endpoints to use Node.js server (app.eventifyplus.com) - Replace 6 mock gamification methods with real API calls (dashboard, leaderboard, shop, redeem, submit) - Add booking models, service, payment service (Razorpay), checkout provider - Add 3-step CheckoutScreen with Razorpay native modal integration - Add Google OAuth login (Flutter + Django backend) - Add full notifications system (Django model + 3 endpoints + Flutter UI) - Register CheckoutProvider, NotificationProvider in main.dart MultiProvider - Wire notification bell in HomeScreen app bar - Add razorpay_flutter ^1.3.7 and google_sign_in ^6.2.2 packages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
147
lib/features/booking/providers/checkout_provider.dart
Normal file
147
lib/features/booking/providers/checkout_provider.dart
Normal file
@@ -0,0 +1,147 @@
|
||||
// lib/features/booking/providers/checkout_provider.dart
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../../../core/utils/error_utils.dart';
|
||||
import '../models/booking_models.dart';
|
||||
import '../services/booking_service.dart';
|
||||
|
||||
enum CheckoutStep { tickets, details, payment, confirmation }
|
||||
|
||||
class CheckoutProvider extends ChangeNotifier {
|
||||
final BookingService _service = BookingService();
|
||||
|
||||
// Event being booked
|
||||
int? eventId;
|
||||
String eventName = '';
|
||||
|
||||
// Step tracking
|
||||
CheckoutStep currentStep = CheckoutStep.tickets;
|
||||
|
||||
// Ticket selection
|
||||
List<TicketMetaModel> availableTickets = [];
|
||||
List<CartItemModel> cart = [];
|
||||
|
||||
// Shipping
|
||||
ShippingDetails? shippingDetails;
|
||||
|
||||
// Coupon
|
||||
String? couponCode;
|
||||
|
||||
// Status
|
||||
bool loading = false;
|
||||
String? error;
|
||||
String? paymentId;
|
||||
|
||||
/// Initialize checkout for an event.
|
||||
Future<void> initForEvent(int eventId, String eventName) async {
|
||||
this.eventId = eventId;
|
||||
this.eventName = eventName;
|
||||
currentStep = CheckoutStep.tickets;
|
||||
cart = [];
|
||||
shippingDetails = null;
|
||||
couponCode = null;
|
||||
paymentId = null;
|
||||
error = null;
|
||||
loading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
availableTickets = await _service.getTicketMeta(eventId);
|
||||
} catch (e) {
|
||||
error = userFriendlyError(e);
|
||||
} finally {
|
||||
loading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Add or update cart item.
|
||||
void setTicketQuantity(TicketMetaModel ticket, int qty) {
|
||||
cart.removeWhere((c) => c.ticket.id == ticket.id);
|
||||
if (qty > 0) {
|
||||
cart.add(CartItemModel(ticket: ticket, quantity: qty));
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
double get subtotal => cart.fold(0, (sum, item) => sum + item.subtotal);
|
||||
double get total => subtotal; // expand with discount/tax later
|
||||
|
||||
bool get hasItems => cart.isNotEmpty;
|
||||
|
||||
/// Move to next step.
|
||||
void nextStep() {
|
||||
if (currentStep == CheckoutStep.tickets && hasItems) {
|
||||
currentStep = CheckoutStep.details;
|
||||
} else if (currentStep == CheckoutStep.details && shippingDetails != null) {
|
||||
currentStep = CheckoutStep.payment;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Move to previous step.
|
||||
void previousStep() {
|
||||
if (currentStep == CheckoutStep.payment) {
|
||||
currentStep = CheckoutStep.details;
|
||||
} else if (currentStep == CheckoutStep.details) {
|
||||
currentStep = CheckoutStep.tickets;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Set shipping details from form.
|
||||
void setShipping(ShippingDetails details) {
|
||||
shippingDetails = details;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Process checkout on backend.
|
||||
Future<Map<String, dynamic>> processCheckout() async {
|
||||
loading = true;
|
||||
error = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final tickets = cart.map((c) => {
|
||||
'ticket_meta_id': c.ticket.id,
|
||||
'quantity': c.quantity,
|
||||
}).toList();
|
||||
|
||||
final res = await _service.processCheckout(
|
||||
eventId: eventId!,
|
||||
tickets: tickets,
|
||||
shippingDetails: shippingDetails?.toJson() ?? {},
|
||||
couponCode: couponCode,
|
||||
);
|
||||
return res;
|
||||
} catch (e) {
|
||||
error = userFriendlyError(e);
|
||||
rethrow;
|
||||
} finally {
|
||||
loading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark payment as complete.
|
||||
void markPaymentSuccess(String id) {
|
||||
paymentId = id;
|
||||
currentStep = CheckoutStep.confirmation;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Reset checkout state.
|
||||
void reset() {
|
||||
eventId = null;
|
||||
eventName = '';
|
||||
currentStep = CheckoutStep.tickets;
|
||||
availableTickets = [];
|
||||
cart = [];
|
||||
shippingDetails = null;
|
||||
couponCode = null;
|
||||
paymentId = null;
|
||||
error = null;
|
||||
loading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user