Feat: Update transaction
This commit is contained in:
parent
606492e8a0
commit
e8763bb85f
|
@ -6,8 +6,11 @@ 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:get/get.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import '../../presentation/controllers/history_controller.dart';
|
||||
|
||||
class TransactionRepositoryImpl implements TransactionRepository {
|
||||
final FirebaseFirestore _firestore;
|
||||
final FirebaseStorage _storage;
|
||||
|
@ -52,6 +55,7 @@ class TransactionRepositoryImpl implements TransactionRepository {
|
|||
|
||||
final transactionData = {
|
||||
'id': transactionId,
|
||||
'idBooking': idBooking,
|
||||
'ticketId': ticketId,
|
||||
'flightId': flightId,
|
||||
'amount': amount,
|
||||
|
@ -355,11 +359,12 @@ class TransactionRepositoryImpl implements TransactionRepository {
|
|||
.orderBy('createdAt', descending: false)
|
||||
.get();
|
||||
|
||||
bool anyTransactionCancelled = false;
|
||||
|
||||
for (var doc in pendingTransactionsSnapshot.docs) {
|
||||
final data = doc.data();
|
||||
final expiryTime = (data['expiryTime'] as Timestamp).toDate();
|
||||
|
||||
// Jika sudah melewati waktu pembayaran
|
||||
if (expiryTime.isBefore(now)) {
|
||||
final ticketId = data['ticketId'];
|
||||
final transactionId = doc.id;
|
||||
|
@ -367,25 +372,27 @@ class TransactionRepositoryImpl implements TransactionRepository {
|
|||
final flightId = data['flightId'];
|
||||
final numberSeat = (data['numberSeat'] as List).map((e) => e.toString()).toList();
|
||||
|
||||
// Update status di Firestore
|
||||
await _firestore.collection('tickets').doc(ticketId).collection('payments').doc(transactionId).update({
|
||||
'status': 'cancelled',
|
||||
'updatedAt': now,
|
||||
});
|
||||
|
||||
// Reset status kursi
|
||||
await _resetSeatStatus(ticketId, flightId, numberSeat);
|
||||
|
||||
// Update status di Realtime Database
|
||||
final databaseRef = FirebaseDatabase.instance.ref();
|
||||
await databaseRef.child('transactions/$userId/$ticketId/$transactionId/payment').update({
|
||||
'status': 'cancelled',
|
||||
'updatedAt': now.millisecondsSinceEpoch,
|
||||
});
|
||||
|
||||
anyTransactionCancelled = true;
|
||||
log('Transaksi $transactionId dibatalkan karena kedaluwarsa');
|
||||
}
|
||||
}
|
||||
|
||||
if (anyTransactionCancelled) {
|
||||
await notifyTransactionUpdate();
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.toString().contains('failed-precondition') || e.toString().contains('requires an index')) {
|
||||
log('[TransactionRepository] Indeks belum tersedia. Menggunakan metode alternatif.');
|
||||
|
@ -496,6 +503,8 @@ class TransactionRepositoryImpl implements TransactionRepository {
|
|||
final ticketsSnapshot = await _firestore.collection('tickets').get();
|
||||
|
||||
int count = 0;
|
||||
bool anyTransactionCancelled = false;
|
||||
|
||||
for (var ticketDoc in ticketsSnapshot.docs) {
|
||||
final ticketId = ticketDoc.id;
|
||||
|
||||
|
@ -543,15 +552,42 @@ class TransactionRepositoryImpl implements TransactionRepository {
|
|||
});
|
||||
}
|
||||
|
||||
anyTransactionCancelled = true;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (anyTransactionCancelled) {
|
||||
await notifyTransactionUpdate();
|
||||
}
|
||||
|
||||
log('[TransactionRepository] Selesai memeriksa transaksi (metode alternatif), $count transaksi dibatalkan');
|
||||
} catch (e) {
|
||||
log('[TransactionRepository] Error pada metode alternatif: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> notifyTransactionUpdate() async {
|
||||
try {
|
||||
final historyController = Get.find<HistoryController>();
|
||||
final userId = historyController.getUserId();
|
||||
|
||||
if (userId.isNotEmpty) {
|
||||
// Jeda sedikit untuk memastikan database sudah diupdate
|
||||
await Future.delayed(Duration(milliseconds: 500));
|
||||
|
||||
// Refresh data
|
||||
await historyController.refreshTransactions(userId);
|
||||
|
||||
// Pastikan UI menampilkan pembaruan
|
||||
historyController.isCheckingExpiry.value = true;
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
historyController.isCheckingExpiry.value = false;
|
||||
}
|
||||
} catch (e) {
|
||||
log('[TransactionRepository] HistoryController tidak tersedia: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,13 @@ class TransactionModel {
|
|||
final String? proofUrl;
|
||||
final DateTime createdAt;
|
||||
final DateTime expiryTime;
|
||||
final int passenger;
|
||||
final Map<String, dynamic> flightDetails;
|
||||
final Map<String, dynamic> bandaraDetails;
|
||||
final Map<String, dynamic>? porterServiceDetails;
|
||||
final Map<String, dynamic> userDetails;
|
||||
final int passenger;
|
||||
final List<Map<String, dynamic>> passengerDetails;
|
||||
final List<String> numberSeat;
|
||||
|
||||
TransactionModel({
|
||||
required this.id,
|
||||
|
@ -28,11 +30,13 @@ class TransactionModel {
|
|||
this.proofUrl,
|
||||
required this.createdAt,
|
||||
required this.expiryTime,
|
||||
required this.passenger,
|
||||
required this.flightDetails,
|
||||
required this.bandaraDetails,
|
||||
this.porterServiceDetails,
|
||||
required this.userDetails,
|
||||
required this.passenger,
|
||||
required this.passengerDetails,
|
||||
required this.numberSeat,
|
||||
});
|
||||
|
||||
factory TransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
|
@ -42,10 +46,31 @@ class TransactionModel {
|
|||
} else if (value is DateTime) {
|
||||
return value;
|
||||
} else {
|
||||
return DateTime.now();
|
||||
return DateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> convertToMapList(dynamic list) {
|
||||
if (list == null) return [];
|
||||
if (list is List) {
|
||||
return list.map((item) {
|
||||
if (item is Map) {
|
||||
return Map<String, dynamic>.from(item);
|
||||
}
|
||||
return <String, dynamic>{};
|
||||
}).toList();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
List<String> convertToStringList(dynamic list) {
|
||||
if (list == null) return [];
|
||||
if (list is List) {
|
||||
return list.map((item) => item.toString()).toList();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
return TransactionModel(
|
||||
id: json['id'] ?? '',
|
||||
idBooking: json['idBooking'] ?? '',
|
||||
|
@ -57,11 +82,13 @@ class TransactionModel {
|
|||
proofUrl: json['proofUrl'],
|
||||
createdAt: getDateTime(json['createdAt']),
|
||||
expiryTime: getDateTime(json['expiryTime']),
|
||||
passenger: json['passenger'] ?? 0,
|
||||
flightDetails: json['flightDetails'] ?? {},
|
||||
bandaraDetails: json['bandaraDetails'] ?? {},
|
||||
porterServiceDetails: json['porterServiceDetails'],
|
||||
userDetails: json['userDetails'] ?? {},
|
||||
passenger: json['passenger'] ?? 0,
|
||||
passengerDetails: convertToMapList(json['passengerDetails']),
|
||||
numberSeat: convertToStringList(json['numberSeat']),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -77,11 +104,13 @@ class TransactionModel {
|
|||
'proofUrl': proofUrl,
|
||||
'createdAt': createdAt,
|
||||
'expiryTime': expiryTime,
|
||||
'passenger': passenger,
|
||||
'flightDetails': flightDetails,
|
||||
'bandaraDetails': bandaraDetails,
|
||||
'porterServiceDetails': porterServiceDetails,
|
||||
'userDetails': userDetails,
|
||||
'passenger': passenger,
|
||||
'passengerDetails': passengerDetails,
|
||||
'numberSeat': numberSeat,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:e_porter/_core/constants/colors.dart';
|
||||
import 'package:e_porter/_core/constants/typography.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
class PaymentCountdownTimer extends StatefulWidget {
|
||||
final DateTime expiryTime;
|
||||
|
||||
const PaymentCountdownTimer({
|
||||
Key? key,
|
||||
required this.expiryTime,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PaymentCountdownTimer> createState() => _PaymentCountdownTimerState();
|
||||
}
|
||||
|
||||
class _PaymentCountdownTimerState extends State<PaymentCountdownTimer> {
|
||||
late Timer _timer;
|
||||
late Duration _remainingTime;
|
||||
bool _isExpired = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_calculateRemainingTime();
|
||||
_startTimer();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _calculateRemainingTime() {
|
||||
final now = DateTime.now();
|
||||
if (widget.expiryTime.isAfter(now)) {
|
||||
_remainingTime = widget.expiryTime.difference(now);
|
||||
} else {
|
||||
_remainingTime = Duration.zero;
|
||||
_isExpired = true;
|
||||
}
|
||||
}
|
||||
|
||||
void _startTimer() {
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_calculateRemainingTime();
|
||||
if (_remainingTime.inSeconds <= 0) {
|
||||
_isExpired = true;
|
||||
_timer.cancel();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
String _formatDuration(Duration duration) {
|
||||
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||
final hours = twoDigits(duration.inHours);
|
||||
final minutes = twoDigits(duration.inMinutes.remainder(60));
|
||||
final seconds = twoDigits(duration.inSeconds.remainder(60));
|
||||
return '$hours:$minutes:$seconds';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
|
||||
decoration: BoxDecoration(
|
||||
color: RedColors.red200,
|
||||
borderRadius: BorderRadius.circular(10.r),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TypographyStyles.caption(
|
||||
"Batas Pembayaran",
|
||||
color: RedColors.red600,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
TypographyStyles.caption(
|
||||
_isExpired ? "Kedaluwarsa" : _formatDuration(_remainingTime),
|
||||
color: RedColors.red600,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue