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:
2026-04-04 15:46:53 +05:30
parent 847577c09d
commit 29e326b8fc
24 changed files with 1663 additions and 164 deletions

View File

@@ -0,0 +1,53 @@
// lib/features/booking/services/booking_service.dart
import '../../../core/api/api_client.dart';
import '../../../core/api/api_endpoints.dart';
import '../models/booking_models.dart';
class BookingService {
final ApiClient _api = ApiClient();
/// Fetch available ticket types for an event.
Future<List<TicketMetaModel>> getTicketMeta(int eventId) async {
final res = await _api.post(
ApiEndpoints.ticketMetaList,
body: {'event_id': eventId},
);
final rawList = res['ticket_metas'] ?? res['tickets'] ?? res['data'] ?? [];
if (rawList is List) {
return rawList
.map((e) => TicketMetaModel.fromJson(Map<String, dynamic>.from(e as Map)))
.toList();
}
return [];
}
/// Add item to cart.
Future<Map<String, dynamic>> addToCart({
required int ticketMetaId,
required int quantity,
}) async {
return await _api.post(
ApiEndpoints.cartAdd,
body: {'ticket_meta_id': ticketMetaId, 'quantity': quantity},
);
}
/// Process checkout — creates booking + returns order ID for payment.
Future<Map<String, dynamic>> processCheckout({
required int eventId,
required List<Map<String, dynamic>> tickets,
required Map<String, dynamic> shippingDetails,
String? couponCode,
}) async {
return await _api.post(
ApiEndpoints.checkout,
body: {
'event_id': eventId,
'tickets': tickets,
'shipping': shippingDetails,
if (couponCode != null) 'coupon_code': couponCode,
},
);
}
}

View File

@@ -0,0 +1,67 @@
// lib/features/booking/services/payment_service.dart
import 'package:razorpay_flutter/razorpay_flutter.dart';
import 'package:flutter/foundation.dart';
typedef PaymentSuccessCallback = void Function(PaymentSuccessResponse response);
typedef PaymentErrorCallback = void Function(PaymentFailureResponse response);
typedef ExternalWalletCallback = void Function(ExternalWalletResponse response);
class PaymentService {
late Razorpay _razorpay;
// Razorpay test key — matches web app
static const String _testKey = 'rzp_test_S49PVZmqAVoWSH';
PaymentSuccessCallback? onSuccess;
PaymentErrorCallback? onError;
ExternalWalletCallback? onExternalWallet;
void initialize({
required PaymentSuccessCallback onSuccess,
required PaymentErrorCallback onError,
ExternalWalletCallback? onExternalWallet,
}) {
_razorpay = Razorpay();
this.onSuccess = onSuccess;
this.onError = onError;
this.onExternalWallet = onExternalWallet;
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handleSuccess);
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handleError);
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
}
void openPayment({
required double amount,
required String email,
required String phone,
required String eventName,
String? orderId,
}) {
final options = <String, dynamic>{
'key': _testKey,
'amount': (amount * 100).toInt(), // paise
'currency': 'INR',
'name': 'Eventify',
'description': 'Ticket: $eventName',
'prefill': {
'email': email,
'contact': phone,
},
'theme': {'color': '#0B63D6'},
};
if (orderId != null) options['order_id'] = orderId;
if (kDebugMode) debugPrint('PaymentService: opening Razorpay with amount=${amount * 100} paise');
_razorpay.open(options);
}
void _handleSuccess(PaymentSuccessResponse res) => onSuccess?.call(res);
void _handleError(PaymentFailureResponse res) => onError?.call(res);
void _handleExternalWallet(ExternalWalletResponse res) => onExternalWallet?.call(res);
void dispose() {
_razorpay.clear();
}
}