Feat: done post data for transaction
This commit is contained in:
parent
afc667838b
commit
18487e7a2e
|
@ -181,6 +181,24 @@
|
||||||
"packageUri": "lib/",
|
"packageUri": "lib/",
|
||||||
"languageVersion": "3.4"
|
"languageVersion": "3.4"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "firebase_database",
|
||||||
|
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_database-11.3.5",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "firebase_database_platform_interface",
|
||||||
|
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_database_platform_interface-0.2.6+5",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "firebase_database_web",
|
||||||
|
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_database_web-0.2.6+11",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "firebase_storage",
|
"name": "firebase_storage",
|
||||||
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_storage-12.4.5",
|
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_storage-12.4.5",
|
||||||
|
@ -199,6 +217,12 @@
|
||||||
"packageUri": "lib/",
|
"packageUri": "lib/",
|
||||||
"languageVersion": "3.4"
|
"languageVersion": "3.4"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "fixnum",
|
||||||
|
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/fixnum-1.1.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "flutter",
|
"name": "flutter",
|
||||||
"rootUri": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0/packages/flutter",
|
"rootUri": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0/packages/flutter",
|
||||||
|
@ -487,6 +511,12 @@
|
||||||
"packageUri": "lib/",
|
"packageUri": "lib/",
|
||||||
"languageVersion": "2.18"
|
"languageVersion": "2.18"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "sprintf",
|
||||||
|
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/sprintf-7.0.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "2.12"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "stack_trace",
|
"name": "stack_trace",
|
||||||
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/stack_trace-1.11.1",
|
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/stack_trace-1.11.1",
|
||||||
|
@ -523,6 +553,12 @@
|
||||||
"packageUri": "lib/",
|
"packageUri": "lib/",
|
||||||
"languageVersion": "3.5"
|
"languageVersion": "3.5"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/uuid-4.5.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.0"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "vector_graphics",
|
"name": "vector_graphics",
|
||||||
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/vector_graphics-1.1.18",
|
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/vector_graphics-1.1.18",
|
||||||
|
@ -596,7 +632,7 @@
|
||||||
"languageVersion": "3.4"
|
"languageVersion": "3.4"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"generated": "2025-03-31T15:19:30.948302Z",
|
"generated": "2025-04-01T21:50:03.833642Z",
|
||||||
"generator": "pub",
|
"generator": "pub",
|
||||||
"generatorVersion": "3.5.0",
|
"generatorVersion": "3.5.0",
|
||||||
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
||||||
|
|
|
@ -118,6 +118,18 @@ firebase_core_web
|
||||||
3.4
|
3.4
|
||||||
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_core_web-2.22.0/
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_core_web-2.22.0/
|
||||||
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_core_web-2.22.0/lib/
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_core_web-2.22.0/lib/
|
||||||
|
firebase_database
|
||||||
|
3.2
|
||||||
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_database-11.3.5/
|
||||||
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_database-11.3.5/lib/
|
||||||
|
firebase_database_platform_interface
|
||||||
|
3.2
|
||||||
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_database_platform_interface-0.2.6+5/
|
||||||
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_database_platform_interface-0.2.6+5/lib/
|
||||||
|
firebase_database_web
|
||||||
|
3.4
|
||||||
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_database_web-0.2.6+11/
|
||||||
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_database_web-0.2.6+11/lib/
|
||||||
firebase_storage
|
firebase_storage
|
||||||
3.2
|
3.2
|
||||||
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_storage-12.4.5/
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_storage-12.4.5/
|
||||||
|
@ -130,6 +142,10 @@ firebase_storage_web
|
||||||
3.4
|
3.4
|
||||||
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_storage_web-3.10.12/
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_storage_web-3.10.12/
|
||||||
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_storage_web-3.10.12/lib/
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/firebase_storage_web-3.10.12/lib/
|
||||||
|
fixnum
|
||||||
|
3.1
|
||||||
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/fixnum-1.1.1/
|
||||||
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/fixnum-1.1.1/lib/
|
||||||
flutter_picker
|
flutter_picker
|
||||||
2.12
|
2.12
|
||||||
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_picker-2.1.0/
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_picker-2.1.0/
|
||||||
|
@ -306,6 +322,10 @@ source_span
|
||||||
2.18
|
2.18
|
||||||
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/source_span-1.10.0/
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/source_span-1.10.0/
|
||||||
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/source_span-1.10.0/lib/
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/source_span-1.10.0/lib/
|
||||||
|
sprintf
|
||||||
|
2.12
|
||||||
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/sprintf-7.0.0/
|
||||||
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/sprintf-7.0.0/lib/
|
||||||
stack_trace
|
stack_trace
|
||||||
2.18
|
2.18
|
||||||
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/stack_trace-1.11.1/
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/stack_trace-1.11.1/
|
||||||
|
@ -330,6 +350,10 @@ typed_data
|
||||||
3.5
|
3.5
|
||||||
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/typed_data-1.4.0/
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/typed_data-1.4.0/
|
||||||
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/typed_data-1.4.0/lib/
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/typed_data-1.4.0/lib/
|
||||||
|
uuid
|
||||||
|
3.0
|
||||||
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/uuid-4.5.1/
|
||||||
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/uuid-4.5.1/lib/
|
||||||
vector_graphics
|
vector_graphics
|
||||||
3.4
|
3.4
|
||||||
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/vector_graphics-1.1.18/
|
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/vector_graphics-1.1.18/
|
||||||
|
|
|
@ -0,0 +1,407 @@
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
|
import 'package:firebase_storage/firebase_storage.dart';
|
||||||
|
import 'package:e_porter/domain/models/transaction_model.dart';
|
||||||
|
import 'package:e_porter/domain/repositories/transaction_repository.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
class TransactionRepositoryImpl implements TransactionRepository {
|
||||||
|
final FirebaseFirestore _firestore;
|
||||||
|
final FirebaseStorage _storage;
|
||||||
|
|
||||||
|
TransactionRepositoryImpl({
|
||||||
|
FirebaseFirestore? firestore,
|
||||||
|
FirebaseStorage? storage,
|
||||||
|
}) : _firestore = firestore ?? FirebaseFirestore.instance,
|
||||||
|
_storage = storage ?? FirebaseStorage.instance;
|
||||||
|
|
||||||
|
// Method untuk generate ID unik
|
||||||
|
String _generateUniqueId() {
|
||||||
|
return DateTime.now().millisecondsSinceEpoch.toString() +
|
||||||
|
'_' +
|
||||||
|
(100000 + (DateTime.now().microsecond % 900000)).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> createTransaction({
|
||||||
|
required String ticketId,
|
||||||
|
required String flightId,
|
||||||
|
required double amount,
|
||||||
|
required String method,
|
||||||
|
required DateTime expiryTime,
|
||||||
|
required Map<String, dynamic> flightDetails,
|
||||||
|
required Map<String, dynamic> bandaraDetails,
|
||||||
|
Map<String, dynamic>? porterServiceDetails,
|
||||||
|
required Map<String, dynamic> userDetails,
|
||||||
|
required int passenger,
|
||||||
|
required List<Map<String, dynamic>> passengerDetails,
|
||||||
|
required List<String> numberSeat,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final transactionId = _generateUniqueId();
|
||||||
|
final now = DateTime.now();
|
||||||
|
|
||||||
|
final transactionData = {
|
||||||
|
'id': transactionId,
|
||||||
|
'ticketId': ticketId,
|
||||||
|
'flightId': flightId,
|
||||||
|
'amount': amount,
|
||||||
|
'method': method,
|
||||||
|
'status': 'pending',
|
||||||
|
'createdAt': now,
|
||||||
|
'expiryTime': expiryTime,
|
||||||
|
'flightDetails': flightDetails,
|
||||||
|
'bandaraDetails': bandaraDetails,
|
||||||
|
'porterServiceDetails': porterServiceDetails,
|
||||||
|
'userDetails': userDetails,
|
||||||
|
'passenger': passenger,
|
||||||
|
'passengerDetails': passengerDetails,
|
||||||
|
'numberSeat': numberSeat,
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firestore
|
||||||
|
.collection('tickets')
|
||||||
|
.doc(ticketId)
|
||||||
|
.collection('payments')
|
||||||
|
.doc(transactionId)
|
||||||
|
.set(transactionData);
|
||||||
|
|
||||||
|
DocumentSnapshot seatData =
|
||||||
|
await _firestore.collection('tickets').doc(ticketId).collection('flights').doc(flightId).get();
|
||||||
|
|
||||||
|
if (seatData.exists) {
|
||||||
|
Map<String, dynamic>? data = seatData.data() as Map<String, dynamic>?;
|
||||||
|
|
||||||
|
// Fungsi untuk mendapatkan kelas seat dari nomor kursi (misalnya "A2" -> "a")
|
||||||
|
String getSeatClass(String seatNumber) {
|
||||||
|
// Ambil huruf pertama dan ubah ke lowercase
|
||||||
|
if (seatNumber.isNotEmpty) {
|
||||||
|
return seatNumber[0].toLowerCase();
|
||||||
|
}
|
||||||
|
return "a"; // Default ke "a" jika format tidak sesuai
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk mendapatkan indeks seat dari nomor kursi (misalnya "A2" -> 2)
|
||||||
|
int getSeatIndex(String seatNumber) {
|
||||||
|
if (seatNumber.length > 1) {
|
||||||
|
try {
|
||||||
|
return int.parse(seatNumber.substring(1)) - 1; // Konversi ke berbasis-0
|
||||||
|
} catch (e) {
|
||||||
|
log("Error parsing seat index: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // Default ke 0 jika format tidak sesuai
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perbarui status kursi untuk setiap seat yang dipilih
|
||||||
|
for (String seatNumber in numberSeat) {
|
||||||
|
String seatClass = getSeatClass(seatNumber);
|
||||||
|
int seatIndex = getSeatIndex(seatNumber);
|
||||||
|
|
||||||
|
log("Processing seat: $seatNumber, class: $seatClass, index: $seatIndex");
|
||||||
|
|
||||||
|
// Pastikan struktur data seat tersedia
|
||||||
|
if (data != null && data.containsKey('seat')) {
|
||||||
|
Map<String, dynamic> seatData = Map<String, dynamic>.from(data['seat']);
|
||||||
|
|
||||||
|
// Pastikan kelas kursi (a-f) tersedia
|
||||||
|
if (seatData.containsKey(seatClass)) {
|
||||||
|
Map<String, dynamic> classSeatData = Map<String, dynamic>.from(seatData[seatClass]);
|
||||||
|
|
||||||
|
// Pastikan array isTaken tersedia
|
||||||
|
if (classSeatData.containsKey('isTaken')) {
|
||||||
|
List<dynamic> isTaken = List<dynamic>.from(classSeatData['isTaken'] ?? []);
|
||||||
|
|
||||||
|
// Perbarui array isTaken
|
||||||
|
while (isTaken.length <= seatIndex) {
|
||||||
|
isTaken.add(false); // Tambahkan seat yang belum ada dengan false
|
||||||
|
}
|
||||||
|
isTaken[seatIndex] = true; // Set kursi yang dipilih sebagai 'taken'
|
||||||
|
|
||||||
|
// Update Firestore untuk kelas kursi tertentu
|
||||||
|
await _firestore
|
||||||
|
.collection('tickets')
|
||||||
|
.doc(ticketId)
|
||||||
|
.collection('flights')
|
||||||
|
.doc(flightId)
|
||||||
|
.update({'seat.$seatClass.isTaken': isTaken});
|
||||||
|
|
||||||
|
log("Successfully updated seat $seatNumber in class $seatClass at index $seatIndex");
|
||||||
|
} else {
|
||||||
|
// Jika 'isTaken' tidak ada, buat array baru
|
||||||
|
List<dynamic> isTaken = List.filled(seatIndex + 1, false);
|
||||||
|
isTaken[seatIndex] = true;
|
||||||
|
|
||||||
|
await _firestore
|
||||||
|
.collection('tickets')
|
||||||
|
.doc(ticketId)
|
||||||
|
.collection('flights')
|
||||||
|
.doc(flightId)
|
||||||
|
.update({'seat.$seatClass.isTaken': isTaken});
|
||||||
|
|
||||||
|
log("Created new isTaken array for seat $seatNumber in class $seatClass");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Jika kelas kursi tidak ada, buat struktur baru
|
||||||
|
List<dynamic> isTaken = List.filled(seatIndex + 1, false);
|
||||||
|
isTaken[seatIndex] = true;
|
||||||
|
|
||||||
|
await _firestore.collection('tickets').doc(ticketId).collection('flights').doc(flightId).update({
|
||||||
|
'seat.$seatClass': {'isTaken': isTaken}
|
||||||
|
});
|
||||||
|
|
||||||
|
log("Created new seat class $seatClass for seat $seatNumber");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Jika struktur 'seat' tidak ada, buat struktur lengkap
|
||||||
|
List<dynamic> isTaken = List.filled(seatIndex + 1, false);
|
||||||
|
isTaken[seatIndex] = true;
|
||||||
|
|
||||||
|
Map<String, dynamic> newSeatData = {
|
||||||
|
'seat': {
|
||||||
|
seatClass: {'isTaken': isTaken}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await _firestore
|
||||||
|
.collection('tickets')
|
||||||
|
.doc(ticketId)
|
||||||
|
.collection('flights')
|
||||||
|
.doc(flightId)
|
||||||
|
.set(newSeatData, SetOptions(merge: true));
|
||||||
|
|
||||||
|
log("Created complete new seat structure for seat $seatNumber");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log("Flight document not found");
|
||||||
|
throw Exception("Flight document not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
final databaseRef = FirebaseDatabase.instance.ref();
|
||||||
|
final userId = userDetails['uid'];
|
||||||
|
await databaseRef.child('transactions/$userId/$ticketId/$transactionId').set({
|
||||||
|
'payment': {
|
||||||
|
'id': transactionId,
|
||||||
|
'status': 'pending',
|
||||||
|
'amount': amount,
|
||||||
|
'method': method,
|
||||||
|
'createdAt': now.millisecondsSinceEpoch,
|
||||||
|
'expiryTime': expiryTime.millisecondsSinceEpoch,
|
||||||
|
},
|
||||||
|
'flight': flightDetails,
|
||||||
|
'bandara': bandaraDetails,
|
||||||
|
'porterService': porterServiceDetails,
|
||||||
|
'user': userDetails,
|
||||||
|
'passenger': passenger,
|
||||||
|
'passengerDetails': passengerDetails,
|
||||||
|
'numberSeat': numberSeat,
|
||||||
|
});
|
||||||
|
|
||||||
|
return transactionId;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to create transaction: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateTransactionStatus({
|
||||||
|
required String ticketId,
|
||||||
|
required String transactionId,
|
||||||
|
required String status,
|
||||||
|
required String userId,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final now = DateTime.now();
|
||||||
|
|
||||||
|
await _firestore.collection('tickets').doc(ticketId).collection('payments').doc(transactionId).update({
|
||||||
|
'status': status,
|
||||||
|
'updatedAt': now,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update status dokumen tiket utama
|
||||||
|
await _firestore.collection('tickets').doc(ticketId).update({
|
||||||
|
'status': status == 'paid' ? 'awaiting_verification' : status,
|
||||||
|
'lastUpdated': now,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update status di Realtime Database
|
||||||
|
final databaseRef = FirebaseDatabase.instance.ref();
|
||||||
|
await databaseRef.child('transactions/$userId/$ticketId/$transactionId/payment').update({
|
||||||
|
'status': status,
|
||||||
|
'updatedAt': now.millisecondsSinceEpoch,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to update transaction status: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> uploadPaymentProof(
|
||||||
|
{required String ticketId, required String transactionId, required File proofImage}) async {
|
||||||
|
try {
|
||||||
|
final fileName =
|
||||||
|
'payment_proof_${transactionId}_${DateTime.now().millisecondsSinceEpoch}${path.extension(proofImage.path)}';
|
||||||
|
final storageRef = _storage.ref().child('payment/$fileName');
|
||||||
|
|
||||||
|
// Upload file ke Firebase Storage
|
||||||
|
final uploadTask = await storageRef.putFile(proofImage);
|
||||||
|
final downloadUrl = await uploadTask.ref.getDownloadURL();
|
||||||
|
|
||||||
|
// Update di Firestore dengan URL bukti pembayaran
|
||||||
|
await _firestore.collection('tickets').doc(ticketId).collection('payments').doc(transactionId).update({
|
||||||
|
'proofUrl': downloadUrl,
|
||||||
|
'status': 'paid',
|
||||||
|
'paidAt': DateTime.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update status dokumen tiket utama
|
||||||
|
await _firestore.collection('tickets').doc(ticketId).update({
|
||||||
|
'status': 'awaiting_verification',
|
||||||
|
'lastUpdated': DateTime.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update di Realtime Database
|
||||||
|
final databaseRef = FirebaseDatabase.instance.ref();
|
||||||
|
await databaseRef.child('transactions/$ticketId/payment').update({
|
||||||
|
'proofUrl': downloadUrl,
|
||||||
|
'status': 'paid',
|
||||||
|
'paidAt': DateTime.now().millisecondsSinceEpoch,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to upload payment proof: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<TransactionModel>> getTransactionsByUserId(String userId) async {
|
||||||
|
try {
|
||||||
|
// Mencari tiket berdasarkan userId
|
||||||
|
final ticketQuerySnapshot = await _firestore.collection('tickets').where('userId', isEqualTo: userId).get();
|
||||||
|
|
||||||
|
final List<TransactionModel> transactions = [];
|
||||||
|
|
||||||
|
// Untuk setiap tiket, ambil data payment
|
||||||
|
for (var ticketDoc in ticketQuerySnapshot.docs) {
|
||||||
|
final ticketId = ticketDoc.id;
|
||||||
|
final paymentsSnapshot = await _firestore.collection('tickets').doc(ticketId).collection('payments').get();
|
||||||
|
|
||||||
|
for (var paymentDoc in paymentsSnapshot.docs) {
|
||||||
|
final data = paymentDoc.data();
|
||||||
|
data['id'] = paymentDoc.id;
|
||||||
|
transactions.add(TransactionModel.fromJson(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to get transactions: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TransactionModel?> getTransactionById({required String ticketId, required String transactionId}) async {
|
||||||
|
try {
|
||||||
|
final docSnapshot =
|
||||||
|
await _firestore.collection('tickets').doc(ticketId).collection('payments').doc(transactionId).get();
|
||||||
|
|
||||||
|
if (docSnapshot.exists) {
|
||||||
|
final data = docSnapshot.data()!;
|
||||||
|
data['id'] = docSnapshot.id;
|
||||||
|
return TransactionModel.fromJson(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to get transaction: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> syncTransactionToRealtimeDB({required String ticketId, required String transactionId}) async {
|
||||||
|
try {
|
||||||
|
final transaction = await getTransactionById(ticketId: ticketId, transactionId: transactionId);
|
||||||
|
|
||||||
|
if (transaction != null) {
|
||||||
|
final databaseRef = FirebaseDatabase.instance.ref();
|
||||||
|
await databaseRef.child('transactions/$ticketId').set({
|
||||||
|
'payment': {
|
||||||
|
'id': transaction.id,
|
||||||
|
'status': transaction.status,
|
||||||
|
'amount': transaction.amount,
|
||||||
|
'method': transaction.method,
|
||||||
|
'proofUrl': transaction.proofUrl,
|
||||||
|
'createdAt': transaction.createdAt.millisecondsSinceEpoch,
|
||||||
|
'expiryTime': transaction.expiryTime.millisecondsSinceEpoch,
|
||||||
|
},
|
||||||
|
'flight': transaction.flightDetails,
|
||||||
|
'bandara': transaction.bandaraDetails,
|
||||||
|
'porterService': transaction.porterServiceDetails,
|
||||||
|
'user': transaction.userDetails,
|
||||||
|
'passenger': transaction.passenger,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to sync transaction to Realtime DB: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<TransactionModel?> watchTransaction({required String ticketId, required String transactionId}) {
|
||||||
|
return _firestore
|
||||||
|
.collection('tickets')
|
||||||
|
.doc(ticketId)
|
||||||
|
.collection('payments')
|
||||||
|
.doc(transactionId)
|
||||||
|
.snapshots()
|
||||||
|
.map((snapshot) {
|
||||||
|
if (snapshot.exists) {
|
||||||
|
final data = snapshot.data()!;
|
||||||
|
data['id'] = snapshot.id;
|
||||||
|
return TransactionModel.fromJson(data);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> checkAndCancelExpiredTransactions() async {
|
||||||
|
try {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final pendingTransactionsSnapshot =
|
||||||
|
await _firestore.collectionGroup('payments').where('status', isEqualTo: 'pending').get();
|
||||||
|
|
||||||
|
for (var doc in pendingTransactionsSnapshot.docs) {
|
||||||
|
final data = doc.data();
|
||||||
|
final expiryTime = (data['expiryTime'] as Timestamp).toDate();
|
||||||
|
|
||||||
|
if (expiryTime.isBefore(now)) {
|
||||||
|
final ticketId = data['ticketId'];
|
||||||
|
final transactionId = doc.id;
|
||||||
|
final userId = data['userDetails']['uid'];
|
||||||
|
|
||||||
|
await _firestore.collection('tickets').doc(ticketId).collection('payments').doc(transactionId).update({
|
||||||
|
'status': 'cancelled',
|
||||||
|
'updatedAt': now,
|
||||||
|
});
|
||||||
|
|
||||||
|
await _firestore.collection('tickets').doc(ticketId).update({
|
||||||
|
'status': 'cancelled',
|
||||||
|
'lastUpdated': now,
|
||||||
|
});
|
||||||
|
|
||||||
|
final databaseRef = FirebaseDatabase.instance.ref();
|
||||||
|
await databaseRef.child('transactions/$userId/$ticketId/$transactionId/payment').update({
|
||||||
|
'status': 'cancelled',
|
||||||
|
'updatedAt': now.millisecondsSinceEpoch,
|
||||||
|
});
|
||||||
|
|
||||||
|
log('Transaction $transactionId cancelled due to expiry');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('Error checking expired transactions: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:e_porter/domain/repositories/transaction_repository.dart';
|
||||||
|
import 'package:e_porter/data/repositories/transaction_repository_impl.dart';
|
||||||
|
import 'package:e_porter/domain/usecases/transaction_usecase.dart';
|
||||||
|
import 'package:e_porter/presentation/controllers/transaction_controller.dart';
|
||||||
|
|
||||||
|
class TransactionBinding extends Bindings {
|
||||||
|
@override
|
||||||
|
void dependencies() {
|
||||||
|
// Repository
|
||||||
|
Get.lazyPut<TransactionRepository>(
|
||||||
|
() => TransactionRepositoryImpl(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// UseCase
|
||||||
|
Get.lazyPut<TransactionUseCase>(
|
||||||
|
() => TransactionUseCase(Get.find<TransactionRepository>()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Controller
|
||||||
|
Get.lazyPut<TransactionController>(
|
||||||
|
() => TransactionController(Get.find<TransactionUseCase>()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
|
||||||
|
class TransactionModel {
|
||||||
|
final String id;
|
||||||
|
final String ticketId;
|
||||||
|
final String flightId;
|
||||||
|
final double amount;
|
||||||
|
final String method;
|
||||||
|
final String status;
|
||||||
|
final String? proofUrl;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final DateTime expiryTime;
|
||||||
|
final Map<String, dynamic> flightDetails;
|
||||||
|
final Map<String, dynamic> bandaraDetails;
|
||||||
|
final Map<String, dynamic>? porterServiceDetails;
|
||||||
|
final Map<String, dynamic> userDetails;
|
||||||
|
final int passenger;
|
||||||
|
|
||||||
|
TransactionModel({
|
||||||
|
required this.id,
|
||||||
|
required this.ticketId,
|
||||||
|
required this.flightId,
|
||||||
|
required this.amount,
|
||||||
|
required this.method,
|
||||||
|
required this.status,
|
||||||
|
this.proofUrl,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.expiryTime,
|
||||||
|
required this.flightDetails,
|
||||||
|
required this.bandaraDetails,
|
||||||
|
this.porterServiceDetails,
|
||||||
|
required this.userDetails,
|
||||||
|
required this.passenger,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory TransactionModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TransactionModel(
|
||||||
|
id: json['id'] ?? '',
|
||||||
|
ticketId: json['ticketId'] ?? '',
|
||||||
|
flightId: json['flightId'] ?? '',
|
||||||
|
amount: (json['amount'] ?? 0.0).toDouble(),
|
||||||
|
method: json['method'] ?? '',
|
||||||
|
status: json['status'] ?? 'pending',
|
||||||
|
proofUrl: json['proofUrl'],
|
||||||
|
createdAt: (json['createdAt'] as Timestamp).toDate(),
|
||||||
|
expiryTime: (json['expiryTime'] as Timestamp).toDate(),
|
||||||
|
flightDetails: json['flightDetails'] ?? {},
|
||||||
|
bandaraDetails: json['bandaraDetails'] ?? {},
|
||||||
|
porterServiceDetails: json['porterServiceDetails'],
|
||||||
|
userDetails: json['userDetails'] ?? {},
|
||||||
|
passenger: json['passenger'] ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'ticketId': ticketId,
|
||||||
|
'flightId': flightId,
|
||||||
|
'amount': amount,
|
||||||
|
'method': method,
|
||||||
|
'status': status,
|
||||||
|
'proofUrl': proofUrl,
|
||||||
|
'createdAt': createdAt,
|
||||||
|
'expiryTime': expiryTime,
|
||||||
|
'flightDetails': flightDetails,
|
||||||
|
'bandaraDetails': bandaraDetails,
|
||||||
|
'porterServiceDetails': porterServiceDetails,
|
||||||
|
'userDetails': userDetails,
|
||||||
|
'passenger': passenger,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:e_porter/domain/models/transaction_model.dart';
|
||||||
|
|
||||||
|
abstract class TransactionRepository {
|
||||||
|
Future<String> createTransaction({
|
||||||
|
required String ticketId,
|
||||||
|
required String flightId,
|
||||||
|
required double amount,
|
||||||
|
required String method,
|
||||||
|
required DateTime expiryTime,
|
||||||
|
required Map<String, dynamic> flightDetails,
|
||||||
|
required Map<String, dynamic> bandaraDetails,
|
||||||
|
Map<String, dynamic>? porterServiceDetails,
|
||||||
|
required Map<String, dynamic> userDetails,
|
||||||
|
required int passenger,
|
||||||
|
required List<Map<String, dynamic>> passengerDetails,
|
||||||
|
required List<String> numberSeat,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> updateTransactionStatus({
|
||||||
|
required String ticketId,
|
||||||
|
required String transactionId,
|
||||||
|
required String status,
|
||||||
|
required String userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> uploadPaymentProof({
|
||||||
|
required String ticketId,
|
||||||
|
required String transactionId,
|
||||||
|
required File proofImage,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<TransactionModel>> getTransactionsByUserId(String userId);
|
||||||
|
|
||||||
|
Future<TransactionModel?> getTransactionById({required String ticketId, required String transactionId});
|
||||||
|
|
||||||
|
Future<void> syncTransactionToRealtimeDB({required String ticketId, required String transactionId});
|
||||||
|
|
||||||
|
Stream<TransactionModel?> watchTransaction({required String ticketId, required String transactionId});
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:e_porter/domain/models/transaction_model.dart';
|
||||||
|
import 'package:e_porter/domain/repositories/transaction_repository.dart';
|
||||||
|
|
||||||
|
class TransactionUseCase {
|
||||||
|
final TransactionRepository _repository;
|
||||||
|
|
||||||
|
TransactionUseCase(this._repository);
|
||||||
|
|
||||||
|
Future<String> createTransaction({
|
||||||
|
required String ticketId,
|
||||||
|
required String flightId,
|
||||||
|
required double amount,
|
||||||
|
required String method,
|
||||||
|
required DateTime expiryTime,
|
||||||
|
required Map<String, dynamic> flightDetails,
|
||||||
|
required Map<String, dynamic> bandaraDetails,
|
||||||
|
Map<String, dynamic>? porterServiceDetails,
|
||||||
|
required Map<String, dynamic> userDetails,
|
||||||
|
required int passenger,
|
||||||
|
required List<Map<String, dynamic>> passengerDetails, // Tambah parameter ini
|
||||||
|
required List<String> numberSeat, // Tambah parameter ini
|
||||||
|
}) {
|
||||||
|
return _repository.createTransaction(
|
||||||
|
ticketId: ticketId,
|
||||||
|
flightId: flightId,
|
||||||
|
amount: amount,
|
||||||
|
method: method,
|
||||||
|
expiryTime: expiryTime,
|
||||||
|
flightDetails: flightDetails,
|
||||||
|
bandaraDetails: bandaraDetails,
|
||||||
|
porterServiceDetails: porterServiceDetails,
|
||||||
|
userDetails: userDetails,
|
||||||
|
passenger: passenger,
|
||||||
|
passengerDetails: passengerDetails, // Tambahkan parameter ini
|
||||||
|
numberSeat: numberSeat, // Tambahkan parameter ini
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateTransactionStatus({
|
||||||
|
required String ticketId,
|
||||||
|
required String transactionId,
|
||||||
|
required String status,
|
||||||
|
required String userId,
|
||||||
|
}) {
|
||||||
|
return _repository.updateTransactionStatus(
|
||||||
|
ticketId: ticketId,
|
||||||
|
transactionId: transactionId,
|
||||||
|
status: status,
|
||||||
|
userId: userId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> uploadPaymentProof({
|
||||||
|
required String ticketId,
|
||||||
|
required String transactionId,
|
||||||
|
required File proofImage,
|
||||||
|
}) {
|
||||||
|
return _repository.uploadPaymentProof(
|
||||||
|
ticketId: ticketId,
|
||||||
|
transactionId: transactionId,
|
||||||
|
proofImage: proofImage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TransactionModel>> getTransactionsByUserId(String userId) {
|
||||||
|
return _repository.getTransactionsByUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<TransactionModel?> getTransactionById({required String ticketId, required String transactionId}) {
|
||||||
|
return _repository.getTransactionById(ticketId: ticketId, transactionId: transactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> syncTransactionToRealtimeDB({required String ticketId, required String transactionId}) {
|
||||||
|
return _repository.syncTransactionToRealtimeDB(ticketId: ticketId, transactionId: transactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<TransactionModel?> watchTransaction({required String ticketId, required String transactionId}) {
|
||||||
|
return _repository.watchTransaction(ticketId: ticketId, transactionId: transactionId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,19 @@
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
import 'package:e_porter/data/repositories/transaction_repository_impl.dart';
|
||||||
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
|
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '_core/service/transaction_expiry_service.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp();
|
await Firebase.initializeApp();
|
||||||
|
|
||||||
|
final transactionRepo = Get.put(TransactionRepositoryImpl());
|
||||||
|
TransactionExpiryService().initialize(transactionRepo);
|
||||||
|
|
||||||
log("Firebase Initialized Successfully!");
|
log("Firebase Initialized Successfully!");
|
||||||
runApp(MyApp(initialRoute: Routes.SPLASH));
|
runApp(MyApp(initialRoute: Routes.SPLASH));
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:e_porter/domain/models/transaction_model.dart';
|
||||||
|
import 'package:e_porter/domain/usecases/transaction_usecase.dart';
|
||||||
|
import 'package:e_porter/_core/service/logger_service.dart';
|
||||||
|
|
||||||
|
class TransactionController extends GetxController {
|
||||||
|
final TransactionUseCase _transactionUseCase;
|
||||||
|
|
||||||
|
TransactionController(this._transactionUseCase);
|
||||||
|
|
||||||
|
final Rx<TransactionModel?> currentTransaction = Rx<TransactionModel?>(null);
|
||||||
|
final RxBool isLoading = false.obs;
|
||||||
|
final RxString error = ''.obs;
|
||||||
|
|
||||||
|
Future<String> createTransaction({
|
||||||
|
required String ticketId,
|
||||||
|
required String flightId,
|
||||||
|
required double amount,
|
||||||
|
required String method,
|
||||||
|
required DateTime expiryTime,
|
||||||
|
required Map<String, dynamic> flightDetails,
|
||||||
|
required Map<String, dynamic> bandaraDetails,
|
||||||
|
Map<String, dynamic>? porterServiceDetails,
|
||||||
|
required Map<String, dynamic> userDetails,
|
||||||
|
required int passenger,
|
||||||
|
required List<Map<String, dynamic>> passengerDetails, // Tambah parameter ini
|
||||||
|
required List<String> numberSeat, // Tambah parameter ini
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
|
||||||
|
final transactionId = await _transactionUseCase.createTransaction(
|
||||||
|
ticketId: ticketId,
|
||||||
|
flightId: flightId,
|
||||||
|
amount: amount,
|
||||||
|
method: method,
|
||||||
|
expiryTime: expiryTime,
|
||||||
|
flightDetails: flightDetails,
|
||||||
|
bandaraDetails: bandaraDetails,
|
||||||
|
porterServiceDetails: porterServiceDetails,
|
||||||
|
userDetails: userDetails,
|
||||||
|
passenger: passenger,
|
||||||
|
passengerDetails: passengerDetails,
|
||||||
|
numberSeat: numberSeat,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ambil data transaksi setelah berhasil dibuat
|
||||||
|
final transaction = await _transactionUseCase.getTransactionById(
|
||||||
|
ticketId: ticketId,
|
||||||
|
transactionId: transactionId,
|
||||||
|
);
|
||||||
|
|
||||||
|
currentTransaction.value = transaction;
|
||||||
|
return transactionId;
|
||||||
|
} catch (e) {
|
||||||
|
logger.e('Gagal membuat transaksi: $e');
|
||||||
|
error.value = 'Gagal membuat transaksi: $e';
|
||||||
|
throw Exception('Gagal membuat transaksi: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> uploadPaymentProof(
|
||||||
|
{required String ticketId, required String transactionId, required File proofImage}) async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
|
||||||
|
await _transactionUseCase.uploadPaymentProof(
|
||||||
|
ticketId: ticketId, transactionId: transactionId, proofImage: proofImage);
|
||||||
|
|
||||||
|
// Refresh data transaksi setelah bukti pembayaran diunggah
|
||||||
|
final transaction =
|
||||||
|
await _transactionUseCase.getTransactionById(ticketId: ticketId, transactionId: transactionId);
|
||||||
|
|
||||||
|
currentTransaction.value = transaction;
|
||||||
|
} catch (e) {
|
||||||
|
logger.e('Gagal mengunggah bukti pembayaran: $e');
|
||||||
|
error.value = 'Gagal mengunggah bukti pembayaran: $e';
|
||||||
|
throw Exception('Gagal mengunggah bukti pembayaran: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TransactionModel>> getTransactionsByUserId(String userId) async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
|
||||||
|
final transactions = await _transactionUseCase.getTransactionsByUserId(userId);
|
||||||
|
return transactions;
|
||||||
|
} catch (e) {
|
||||||
|
logger.e('Gagal mendapatkan daftar transaksi: $e');
|
||||||
|
error.value = 'Gagal mendapatkan daftar transaksi: $e';
|
||||||
|
throw Exception('Gagal mendapatkan daftar transaksi: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getTransactionById({required String ticketId, required String transactionId}) async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
|
||||||
|
final transaction =
|
||||||
|
await _transactionUseCase.getTransactionById(ticketId: ticketId, transactionId: transactionId);
|
||||||
|
|
||||||
|
currentTransaction.value = transaction;
|
||||||
|
} catch (e) {
|
||||||
|
logger.e('Gagal mendapatkan detail transaksi: $e');
|
||||||
|
error.value = 'Gagal mendapatkan detail transaksi: $e';
|
||||||
|
throw Exception('Gagal mendapatkan detail transaksi: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void watchTransaction({required String ticketId, required String transactionId}) {
|
||||||
|
try {
|
||||||
|
_transactionUseCase.watchTransaction(ticketId: ticketId, transactionId: transactionId).listen(
|
||||||
|
(transaction) {
|
||||||
|
currentTransaction.value = transaction;
|
||||||
|
},
|
||||||
|
onError: (e) {
|
||||||
|
logger.e('Error mendengarkan perubahan transaksi: $e');
|
||||||
|
error.value = 'Error mendengarkan perubahan transaksi: $e';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logger.e('Gagal memantau transaksi: $e');
|
||||||
|
error.value = 'Gagal memantau transaksi: $e';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateTransactionStatus({
|
||||||
|
required String ticketId,
|
||||||
|
required String transactionId,
|
||||||
|
required String status,
|
||||||
|
required String userId,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
|
||||||
|
await _transactionUseCase.updateTransactionStatus(
|
||||||
|
ticketId: ticketId,
|
||||||
|
transactionId: transactionId,
|
||||||
|
status: status,
|
||||||
|
userId: userId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Refresh data transaksi setelah status diupdate
|
||||||
|
final transaction = await _transactionUseCase.getTransactionById(
|
||||||
|
ticketId: ticketId,
|
||||||
|
transactionId: transactionId,
|
||||||
|
);
|
||||||
|
|
||||||
|
currentTransaction.value = transaction;
|
||||||
|
} catch (e) {
|
||||||
|
logger.e('Gagal mengupdate status transaksi: $e');
|
||||||
|
error.value = 'Gagal mengupdate status transaksi: $e';
|
||||||
|
throw Exception('Gagal mengupdate status transaksi: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,10 +13,13 @@ import 'package:intl/intl.dart';
|
||||||
import '../../../../_core/component/appbar/appbar_component.dart';
|
import '../../../../_core/component/appbar/appbar_component.dart';
|
||||||
import '../../../../_core/component/icons/icons_library.dart';
|
import '../../../../_core/component/icons/icons_library.dart';
|
||||||
import '../../../../_core/service/logger_service.dart';
|
import '../../../../_core/service/logger_service.dart';
|
||||||
|
import '../../../../_core/service/preferences_service.dart';
|
||||||
|
import '../../../../_core/utils/snackbar/snackbar_helper.dart';
|
||||||
import '../../../../domain/models/porter_service_model.dart';
|
import '../../../../domain/models/porter_service_model.dart';
|
||||||
import '../../../../domain/models/ticket_model.dart';
|
import '../../../../domain/models/ticket_model.dart';
|
||||||
import '../../../../domain/models/user_entity.dart';
|
import '../../../../domain/models/user_entity.dart';
|
||||||
import '../../../controllers/ticket_controller.dart';
|
import '../../../controllers/ticket_controller.dart';
|
||||||
|
import '../../../controllers/transaction_controller.dart';
|
||||||
import '../component/card_flight_information.dart';
|
import '../component/card_flight_information.dart';
|
||||||
|
|
||||||
class TicketBookingStep4Screen extends StatefulWidget {
|
class TicketBookingStep4Screen extends StatefulWidget {
|
||||||
|
@ -41,6 +44,7 @@ class _TicketBookingStep4ScreenState extends State<TicketBookingStep4Screen> {
|
||||||
final double serviceCharge = 10000.0;
|
final double serviceCharge = 10000.0;
|
||||||
|
|
||||||
final TicketController ticketController = Get.find<TicketController>();
|
final TicketController ticketController = Get.find<TicketController>();
|
||||||
|
final TransactionController transactionController = Get.find<TransactionController>();
|
||||||
FlightModel? flightData;
|
FlightModel? flightData;
|
||||||
String? departureTime;
|
String? departureTime;
|
||||||
String? arrivalTime;
|
String? arrivalTime;
|
||||||
|
@ -218,26 +222,137 @@ class _TicketBookingStep4ScreenState extends State<TicketBookingStep4Screen> {
|
||||||
price: "Rp ${NumberFormat.decimalPattern('id_ID').format(totalAll())}",
|
price: "Rp ${NumberFormat.decimalPattern('id_ID').format(totalAll())}",
|
||||||
labelButton: "Buat Pesanan",
|
labelButton: "Buat Pesanan",
|
||||||
iconButton: CustomeIcons.ProtectOutline(color: Colors.white),
|
iconButton: CustomeIcons.ProtectOutline(color: Colors.white),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
final DateTime currentTime = DateTime.now();
|
try {
|
||||||
final DateTime expiryTime = currentTime.add(Duration(hours: 1));
|
Get.dialog(
|
||||||
final String formattedExpiryTime =
|
Center(child: CircularProgressIndicator()),
|
||||||
"${DateFormat('dd MMMM yyyy', 'en_US').format(expiryTime)}, ${DateFormat.Hm().format(expiryTime)}";
|
barrierDismissible: false,
|
||||||
final argument = {
|
);
|
||||||
'ticketId': ticketId,
|
|
||||||
'flightId': flightId,
|
final userData = await PreferencesService.getUserData();
|
||||||
'date': ticketDate,
|
if (userData == null) {
|
||||||
'passenger': passenger,
|
Get.back(); // close dialog
|
||||||
'selectedPassenger': selectedPassengers,
|
SnackbarHelper.showError('Error', 'Data pengguna tidak ditemukan');
|
||||||
'numberSeat': numberSeat,
|
return;
|
||||||
'totalPrice': totalPrice,
|
}
|
||||||
'grandTotal': grandTotal,
|
|
||||||
'selectedServiceLabels': selectedServiceLabels,
|
// Persiapkan data expiry time
|
||||||
'selectedPorterServices': selectedPorterServices,
|
final DateTime currentTime = DateTime.now();
|
||||||
'totalAll': totalAll(),
|
final DateTime expiryTime = currentTime.add(Duration(seconds: 5));
|
||||||
'expiryTime': formattedExpiryTime,
|
|
||||||
};
|
// Persiapkan data bandara
|
||||||
Get.toNamed(Routes.PAYMENT, arguments: argument);
|
final bandaraData = {
|
||||||
|
'departure': {
|
||||||
|
'code': flightData?.codeDeparture,
|
||||||
|
'city': flightData?.cityDeparture,
|
||||||
|
},
|
||||||
|
'arrival': {
|
||||||
|
'code': flightData?.codeArrival,
|
||||||
|
'city': flightData?.cityArrival,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Persiapkan data flight
|
||||||
|
final flightDataMap = {
|
||||||
|
'airLines': flightData?.airLines,
|
||||||
|
'code': flightData?.code,
|
||||||
|
'cityDeparture': flightData?.cityDeparture,
|
||||||
|
'cityArrival': flightData?.cityArrival,
|
||||||
|
'codeDeparture': flightData?.codeDeparture,
|
||||||
|
'codeArrival': flightData?.codeArrival,
|
||||||
|
'departureTime': departureTime,
|
||||||
|
'arrivalTime': arrivalTime,
|
||||||
|
'flightClass': flightData?.flightClass,
|
||||||
|
'transitAirplane': flightData?.transitAirplane,
|
||||||
|
'stop': flightData?.stop,
|
||||||
|
'airlineLogo': flightData?.airlineLogo,
|
||||||
|
'price': flightData?.price,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Persiapkan data porter service jika ada
|
||||||
|
Map<String, dynamic>? porterServiceData;
|
||||||
|
if (selectedPorterServices.isNotEmpty) {
|
||||||
|
porterServiceData = {};
|
||||||
|
selectedPorterServices.forEach((key, value) {
|
||||||
|
if (value != null) {
|
||||||
|
porterServiceData![key] = {
|
||||||
|
'name': value.name,
|
||||||
|
'price': value.price,
|
||||||
|
'description': value.description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persiapkan data user
|
||||||
|
final userDetailData = {
|
||||||
|
'uid': userData.uid,
|
||||||
|
'name': userData.name,
|
||||||
|
'email': userData.email,
|
||||||
|
'phone': userData.phone,
|
||||||
|
};
|
||||||
|
|
||||||
|
final List<Map<String, dynamic>> passengerDetailsList = [];
|
||||||
|
for (var passenger in selectedPassengers) {
|
||||||
|
if (passenger != null) {
|
||||||
|
passengerDetailsList.add({
|
||||||
|
'name': passenger.name,
|
||||||
|
'typeId': passenger.typeId,
|
||||||
|
'noId': passenger.noId,
|
||||||
|
'gender': passenger.gender,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buat transaksi
|
||||||
|
final transactionId = await transactionController.createTransaction(
|
||||||
|
ticketId: ticketId,
|
||||||
|
flightId: flightId,
|
||||||
|
amount: totalAll(),
|
||||||
|
method: 'QRIS',
|
||||||
|
expiryTime: expiryTime,
|
||||||
|
flightDetails: flightDataMap,
|
||||||
|
bandaraDetails: bandaraData,
|
||||||
|
porterServiceDetails: porterServiceData,
|
||||||
|
userDetails: userDetailData,
|
||||||
|
passenger: passenger,
|
||||||
|
passengerDetails: passengerDetailsList,
|
||||||
|
numberSeat: numberSeat,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tutup dialog loading
|
||||||
|
Get.back();
|
||||||
|
|
||||||
|
// Format expiry time untuk tampilan
|
||||||
|
final formattedExpiryTime =
|
||||||
|
"${DateFormat('dd MMMM yyyy', 'en_US').format(expiryTime)}, ${DateFormat.Hm().format(expiryTime)}";
|
||||||
|
|
||||||
|
// Navigasi ke halaman pembayaran
|
||||||
|
final argument = {
|
||||||
|
'ticketId': ticketId,
|
||||||
|
'flightId': flightId,
|
||||||
|
'transactionId': transactionId,
|
||||||
|
'date': ticketDate,
|
||||||
|
'passenger': passenger,
|
||||||
|
'selectedPassenger': selectedPassengers,
|
||||||
|
'numberSeat': numberSeat,
|
||||||
|
'totalPrice': totalPrice,
|
||||||
|
'grandTotal': grandTotal,
|
||||||
|
'selectedServiceLabels': selectedServiceLabels,
|
||||||
|
'selectedPorterServices': selectedPorterServices,
|
||||||
|
'totalAll': totalAll(),
|
||||||
|
'expiryTime': formattedExpiryTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
Get.toNamed(Routes.PAYMENT, arguments: argument);
|
||||||
|
} catch (e) {
|
||||||
|
// Tutup dialog loading jika terjadi error
|
||||||
|
if (Get.isDialogOpen ?? false) {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
SnackbarHelper.showError('Error', 'Gagal membuat pesanan: $e');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:e_porter/domain/bindings/porter_service_binding.dart';
|
||||||
import 'package:e_porter/domain/bindings/profil_binding.dart';
|
import 'package:e_porter/domain/bindings/profil_binding.dart';
|
||||||
import 'package:e_porter/domain/bindings/search_flight_binding.dart';
|
import 'package:e_porter/domain/bindings/search_flight_binding.dart';
|
||||||
import 'package:e_porter/domain/bindings/ticket_binding.dart';
|
import 'package:e_porter/domain/bindings/ticket_binding.dart';
|
||||||
|
import 'package:e_porter/domain/bindings/transaction_binding.dart';
|
||||||
import 'package:e_porter/presentation/screens/auth/pages/forget_password_screen.dart';
|
import 'package:e_porter/presentation/screens/auth/pages/forget_password_screen.dart';
|
||||||
import 'package:e_porter/presentation/screens/auth/pages/login_screen.dart';
|
import 'package:e_porter/presentation/screens/auth/pages/login_screen.dart';
|
||||||
import 'package:e_porter/presentation/screens/auth/pages/register_screen.dart';
|
import 'package:e_porter/presentation/screens/auth/pages/register_screen.dart';
|
||||||
|
@ -112,6 +113,7 @@ class AppRoutes {
|
||||||
GetPage(
|
GetPage(
|
||||||
name: Routes.TICKETBOOKINGSTEP4,
|
name: Routes.TICKETBOOKINGSTEP4,
|
||||||
page: () => TicketBookingStep4Screen(),
|
page: () => TicketBookingStep4Screen(),
|
||||||
|
binding: TransactionBinding(),
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: Routes.CHOOSECHAIR,
|
name: Routes.CHOOSECHAIR,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import device_info_plus
|
||||||
import file_picker
|
import file_picker
|
||||||
import firebase_auth
|
import firebase_auth
|
||||||
import firebase_core
|
import firebase_core
|
||||||
|
import firebase_database
|
||||||
import firebase_storage
|
import firebase_storage
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
|
@ -20,6 +21,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||||
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
|
FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin"))
|
||||||
FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin"))
|
FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
|
|
48
pubspec.lock
48
pubspec.lock
|
@ -241,6 +241,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.22.0"
|
version: "2.22.0"
|
||||||
|
firebase_database:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_database
|
||||||
|
sha256: "182ce4713d47ffc5f19a5a7b934867d1fae9c33081febcec8c062cb89fc14652"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.3.5"
|
||||||
|
firebase_database_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_database_platform_interface
|
||||||
|
sha256: b65f416dd2c8ac2d5322241e5411a24ed3da43d0f38aaf9ab6c211d72e52261b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.6+5"
|
||||||
|
firebase_database_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_database_web
|
||||||
|
sha256: "5203141fe00a1edfaed5f8e0444b8e4ef807a8ec6eca925621b1cab69b6c06e4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.6+11"
|
||||||
firebase_storage:
|
firebase_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -265,6 +289,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.10.12"
|
version: "3.10.12"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -637,6 +669,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.10.0"
|
||||||
|
sprintf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sprintf
|
||||||
|
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -685,6 +725,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
uuid:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.1"
|
||||||
vector_graphics:
|
vector_graphics:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -47,6 +47,8 @@ dependencies:
|
||||||
cloud_firestore: ^5.6.4
|
cloud_firestore: ^5.6.4
|
||||||
firebase_auth: ^5.5.1
|
firebase_auth: ^5.5.1
|
||||||
firebase_storage: ^12.4.4
|
firebase_storage: ^12.4.4
|
||||||
|
uuid: ^4.5.1
|
||||||
|
firebase_database: ^11.3.5
|
||||||
shared_preferences: ^2.4.6
|
shared_preferences: ^2.4.6
|
||||||
logger: ^2.5.0
|
logger: ^2.5.0
|
||||||
dropdown_button2: ^2.3.9
|
dropdown_button2: ^2.3.9
|
||||||
|
|
Loading…
Reference in New Issue