feat: implement idempotency key for critical API endpoints
This commit is contained in:
parent
2ab20a1150
commit
b228538725
@ -3,6 +3,7 @@ import 'package:dartz/dartz.dart' hide Order;
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:injectable/injectable.dart' hide Order;
|
import 'package:injectable/injectable.dart' hide Order;
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../../../common/types/order_type.dart';
|
import '../../../common/types/order_type.dart';
|
||||||
import '../../../domain/customer/customer.dart';
|
import '../../../domain/customer/customer.dart';
|
||||||
@ -123,10 +124,15 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
|
|||||||
addItemOrder: (e) async {
|
addItemOrder: (e) async {
|
||||||
Either<OrderFailure, Order> failureOrAddItemOrder;
|
Either<OrderFailure, Order> failureOrAddItemOrder;
|
||||||
|
|
||||||
|
// Generate new idempotency key saat user intent (tap Add Items)
|
||||||
|
// Kalau sedang retry, pakai key yang sama
|
||||||
|
final idempotencyKey = state.addItemIdempotencyKey ?? const Uuid().v4();
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
isAddingItemOrder: true,
|
isAddingItemOrder: true,
|
||||||
failureOrAddItemOrder: none(),
|
failureOrAddItemOrder: none(),
|
||||||
|
addItemIdempotencyKey: idempotencyKey,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -145,12 +151,16 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
|
|||||||
failureOrAddItemOrder = await _repository.addItemOrder(
|
failureOrAddItemOrder = await _repository.addItemOrder(
|
||||||
id: e.orderId,
|
id: e.orderId,
|
||||||
request: request,
|
request: request,
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
isAddingItemOrder: false,
|
isAddingItemOrder: false,
|
||||||
failureOrAddItemOrder: optionOf(failureOrAddItemOrder),
|
failureOrAddItemOrder: optionOf(failureOrAddItemOrder),
|
||||||
|
// Clear key on success, keep on failure for retry
|
||||||
|
addItemIdempotencyKey:
|
||||||
|
failureOrAddItemOrder.isRight() ? null : idempotencyKey,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1584,6 +1584,11 @@ mixin _$OrderFormState {
|
|||||||
bool get isCreatingWithPayment => throw _privateConstructorUsedError;
|
bool get isCreatingWithPayment => throw _privateConstructorUsedError;
|
||||||
bool get isAddingItemOrder => throw _privateConstructorUsedError;
|
bool get isAddingItemOrder => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Idempotency key untuk add item order.
|
||||||
|
/// Di-generate saat user tap "Add Items", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses.
|
||||||
|
String? get addItemIdempotencyKey => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Create a copy of OrderFormState
|
/// Create a copy of OrderFormState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -1608,6 +1613,7 @@ abstract class $OrderFormStateCopyWith<$Res> {
|
|||||||
bool isCreating,
|
bool isCreating,
|
||||||
bool isCreatingWithPayment,
|
bool isCreatingWithPayment,
|
||||||
bool isAddingItemOrder,
|
bool isAddingItemOrder,
|
||||||
|
String? addItemIdempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
$PaymentMethodCopyWith<$Res>? get paymentMethod;
|
$PaymentMethodCopyWith<$Res>? get paymentMethod;
|
||||||
@ -1638,6 +1644,7 @@ class _$OrderFormStateCopyWithImpl<$Res, $Val extends OrderFormState>
|
|||||||
Object? isCreating = null,
|
Object? isCreating = null,
|
||||||
Object? isCreatingWithPayment = null,
|
Object? isCreatingWithPayment = null,
|
||||||
Object? isAddingItemOrder = null,
|
Object? isAddingItemOrder = null,
|
||||||
|
Object? addItemIdempotencyKey = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@ -1678,6 +1685,10 @@ class _$OrderFormStateCopyWithImpl<$Res, $Val extends OrderFormState>
|
|||||||
? _value.isAddingItemOrder
|
? _value.isAddingItemOrder
|
||||||
: isAddingItemOrder // ignore: cast_nullable_to_non_nullable
|
: isAddingItemOrder // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
addItemIdempotencyKey: freezed == addItemIdempotencyKey
|
||||||
|
? _value.addItemIdempotencyKey
|
||||||
|
: addItemIdempotencyKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@ -1731,6 +1742,7 @@ abstract class _$$OrderFormStateImplCopyWith<$Res>
|
|||||||
bool isCreating,
|
bool isCreating,
|
||||||
bool isCreatingWithPayment,
|
bool isCreatingWithPayment,
|
||||||
bool isAddingItemOrder,
|
bool isAddingItemOrder,
|
||||||
|
String? addItemIdempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1762,6 +1774,7 @@ class __$$OrderFormStateImplCopyWithImpl<$Res>
|
|||||||
Object? isCreating = null,
|
Object? isCreating = null,
|
||||||
Object? isCreatingWithPayment = null,
|
Object? isCreatingWithPayment = null,
|
||||||
Object? isAddingItemOrder = null,
|
Object? isAddingItemOrder = null,
|
||||||
|
Object? addItemIdempotencyKey = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$OrderFormStateImpl(
|
_$OrderFormStateImpl(
|
||||||
@ -1801,6 +1814,10 @@ class __$$OrderFormStateImplCopyWithImpl<$Res>
|
|||||||
? _value.isAddingItemOrder
|
? _value.isAddingItemOrder
|
||||||
: isAddingItemOrder // ignore: cast_nullable_to_non_nullable
|
: isAddingItemOrder // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
addItemIdempotencyKey: freezed == addItemIdempotencyKey
|
||||||
|
? _value.addItemIdempotencyKey
|
||||||
|
: addItemIdempotencyKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1821,6 +1838,7 @@ class _$OrderFormStateImpl
|
|||||||
this.isCreating = false,
|
this.isCreating = false,
|
||||||
this.isCreatingWithPayment = false,
|
this.isCreatingWithPayment = false,
|
||||||
this.isAddingItemOrder = false,
|
this.isAddingItemOrder = false,
|
||||||
|
this.addItemIdempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1845,9 +1863,15 @@ class _$OrderFormStateImpl
|
|||||||
@JsonKey()
|
@JsonKey()
|
||||||
final bool isAddingItemOrder;
|
final bool isAddingItemOrder;
|
||||||
|
|
||||||
|
/// Idempotency key untuk add item order.
|
||||||
|
/// Di-generate saat user tap "Add Items", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses.
|
||||||
|
@override
|
||||||
|
final String? addItemIdempotencyKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||||
return 'OrderFormState(paymentMethod: $paymentMethod, customerName: $customerName, customer: $customer, failureOrCreateOrder: $failureOrCreateOrder, failureOrCreateOrderWithPayment: $failureOrCreateOrderWithPayment, failureOrAddItemOrder: $failureOrAddItemOrder, isCreating: $isCreating, isCreatingWithPayment: $isCreatingWithPayment, isAddingItemOrder: $isAddingItemOrder)';
|
return 'OrderFormState(paymentMethod: $paymentMethod, customerName: $customerName, customer: $customer, failureOrCreateOrder: $failureOrCreateOrder, failureOrCreateOrderWithPayment: $failureOrCreateOrderWithPayment, failureOrAddItemOrder: $failureOrAddItemOrder, isCreating: $isCreating, isCreatingWithPayment: $isCreatingWithPayment, isAddingItemOrder: $isAddingItemOrder, addItemIdempotencyKey: $addItemIdempotencyKey)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1868,7 +1892,10 @@ class _$OrderFormStateImpl
|
|||||||
..add(DiagnosticsProperty('failureOrAddItemOrder', failureOrAddItemOrder))
|
..add(DiagnosticsProperty('failureOrAddItemOrder', failureOrAddItemOrder))
|
||||||
..add(DiagnosticsProperty('isCreating', isCreating))
|
..add(DiagnosticsProperty('isCreating', isCreating))
|
||||||
..add(DiagnosticsProperty('isCreatingWithPayment', isCreatingWithPayment))
|
..add(DiagnosticsProperty('isCreatingWithPayment', isCreatingWithPayment))
|
||||||
..add(DiagnosticsProperty('isAddingItemOrder', isAddingItemOrder));
|
..add(DiagnosticsProperty('isAddingItemOrder', isAddingItemOrder))
|
||||||
|
..add(
|
||||||
|
DiagnosticsProperty('addItemIdempotencyKey', addItemIdempotencyKey),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1897,7 +1924,9 @@ class _$OrderFormStateImpl
|
|||||||
(identical(other.isCreatingWithPayment, isCreatingWithPayment) ||
|
(identical(other.isCreatingWithPayment, isCreatingWithPayment) ||
|
||||||
other.isCreatingWithPayment == isCreatingWithPayment) &&
|
other.isCreatingWithPayment == isCreatingWithPayment) &&
|
||||||
(identical(other.isAddingItemOrder, isAddingItemOrder) ||
|
(identical(other.isAddingItemOrder, isAddingItemOrder) ||
|
||||||
other.isAddingItemOrder == isAddingItemOrder));
|
other.isAddingItemOrder == isAddingItemOrder) &&
|
||||||
|
(identical(other.addItemIdempotencyKey, addItemIdempotencyKey) ||
|
||||||
|
other.addItemIdempotencyKey == addItemIdempotencyKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1912,6 +1941,7 @@ class _$OrderFormStateImpl
|
|||||||
isCreating,
|
isCreating,
|
||||||
isCreatingWithPayment,
|
isCreatingWithPayment,
|
||||||
isAddingItemOrder,
|
isAddingItemOrder,
|
||||||
|
addItemIdempotencyKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Create a copy of OrderFormState
|
/// Create a copy of OrderFormState
|
||||||
@ -1938,6 +1968,7 @@ abstract class _OrderFormState implements OrderFormState {
|
|||||||
final bool isCreating,
|
final bool isCreating,
|
||||||
final bool isCreatingWithPayment,
|
final bool isCreatingWithPayment,
|
||||||
final bool isAddingItemOrder,
|
final bool isAddingItemOrder,
|
||||||
|
final String? addItemIdempotencyKey,
|
||||||
}) = _$OrderFormStateImpl;
|
}) = _$OrderFormStateImpl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1959,6 +1990,12 @@ abstract class _OrderFormState implements OrderFormState {
|
|||||||
@override
|
@override
|
||||||
bool get isAddingItemOrder;
|
bool get isAddingItemOrder;
|
||||||
|
|
||||||
|
/// Idempotency key untuk add item order.
|
||||||
|
/// Di-generate saat user tap "Add Items", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses.
|
||||||
|
@override
|
||||||
|
String? get addItemIdempotencyKey;
|
||||||
|
|
||||||
/// Create a copy of OrderFormState
|
/// Create a copy of OrderFormState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -13,6 +13,10 @@ class OrderFormState with _$OrderFormState {
|
|||||||
@Default(false) bool isCreating,
|
@Default(false) bool isCreating,
|
||||||
@Default(false) bool isCreatingWithPayment,
|
@Default(false) bool isCreatingWithPayment,
|
||||||
@Default(false) bool isAddingItemOrder,
|
@Default(false) bool isAddingItemOrder,
|
||||||
|
/// Idempotency key untuk add item order.
|
||||||
|
/// Di-generate saat user tap "Add Items", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses.
|
||||||
|
String? addItemIdempotencyKey,
|
||||||
}) = _OrderFormState;
|
}) = _OrderFormState;
|
||||||
|
|
||||||
factory OrderFormState.initial() => OrderFormState(
|
factory OrderFormState.initial() => OrderFormState(
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import 'package:bloc/bloc.dart';
|
|||||||
import 'package:dartz/dartz.dart' hide Order;
|
import 'package:dartz/dartz.dart' hide Order;
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:injectable/injectable.dart' hide Order;
|
import 'package:injectable/injectable.dart' hide Order;
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../../../common/types/split_type.dart';
|
import '../../../common/types/split_type.dart';
|
||||||
import '../../../domain/order/order.dart';
|
import '../../../domain/order/order.dart';
|
||||||
@ -38,7 +39,15 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
|
|||||||
submitted: (e) async {
|
submitted: (e) async {
|
||||||
Either<OrderFailure, Payment> failureOrPayment;
|
Either<OrderFailure, Payment> failureOrPayment;
|
||||||
|
|
||||||
emit(state.copyWith(isSubmitting: true, failureOrPayment: none()));
|
// Generate new idempotency key saat user intent (tap Bayar)
|
||||||
|
// Kalau sedang retry (isSubmitting sebelumnya gagal), pakai key yang sama
|
||||||
|
final idempotencyKey = state.idempotencyKey ?? const Uuid().v4();
|
||||||
|
|
||||||
|
emit(state.copyWith(
|
||||||
|
isSubmitting: true,
|
||||||
|
failureOrPayment: none(),
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
|
));
|
||||||
|
|
||||||
final request = PaymentRequest(
|
final request = PaymentRequest(
|
||||||
orderId: state.order.id,
|
orderId: state.order.id,
|
||||||
@ -58,20 +67,32 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
|
|||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
failureOrPayment = await _repository.createPayment(request: request);
|
failureOrPayment = await _repository.createPayment(
|
||||||
|
request: request,
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
|
);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
failureOrPayment: optionOf(failureOrPayment),
|
failureOrPayment: optionOf(failureOrPayment),
|
||||||
|
// Clear key on success, keep on failure for retry
|
||||||
|
idempotencyKey: failureOrPayment.isRight() ? null : idempotencyKey,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
submittedSplitBill: (e) async {
|
submittedSplitBill: (e) async {
|
||||||
Either<OrderFailure, Payment> failureOrPayment;
|
Either<OrderFailure, Payment> failureOrPayment;
|
||||||
|
|
||||||
|
// Generate new idempotency key untuk split bill
|
||||||
|
final idempotencyKey = state.splitBillIdempotencyKey ?? const Uuid().v4();
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(isSubmitting: true, failureOrPaymentSplitBill: none()),
|
state.copyWith(
|
||||||
|
isSubmitting: true,
|
||||||
|
failureOrPaymentSplitBill: none(),
|
||||||
|
splitBillIdempotencyKey: idempotencyKey,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final request = PaymentSplitBillRequest(
|
final request = PaymentSplitBillRequest(
|
||||||
@ -93,12 +114,18 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
|
|||||||
|
|
||||||
log(request.toString());
|
log(request.toString());
|
||||||
|
|
||||||
failureOrPayment = await _repository.createSplitBill(request);
|
failureOrPayment = await _repository.createSplitBill(
|
||||||
|
request,
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
|
);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
failureOrPaymentSplitBill: optionOf(failureOrPayment),
|
failureOrPaymentSplitBill: optionOf(failureOrPayment),
|
||||||
|
// Clear key on success, keep on failure for retry
|
||||||
|
splitBillIdempotencyKey:
|
||||||
|
failureOrPayment.isRight() ? null : idempotencyKey,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -813,6 +813,14 @@ mixin _$PaymentFormState {
|
|||||||
PaymentMethod? get paymentMethod => throw _privateConstructorUsedError;
|
PaymentMethod? get paymentMethod => throw _privateConstructorUsedError;
|
||||||
bool get isSubmitting => throw _privateConstructorUsedError;
|
bool get isSubmitting => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Idempotency key untuk payment submission.
|
||||||
|
/// Di-generate saat user tap "Bayar", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses.
|
||||||
|
String? get idempotencyKey => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Idempotency key untuk split bill submission.
|
||||||
|
String? get splitBillIdempotencyKey => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Create a copy of PaymentFormState
|
/// Create a copy of PaymentFormState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -834,6 +842,8 @@ abstract class $PaymentFormStateCopyWith<$Res> {
|
|||||||
Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
|
Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
|
||||||
PaymentMethod? paymentMethod,
|
PaymentMethod? paymentMethod,
|
||||||
bool isSubmitting,
|
bool isSubmitting,
|
||||||
|
String? idempotencyKey,
|
||||||
|
String? splitBillIdempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
$OrderCopyWith<$Res> get order;
|
$OrderCopyWith<$Res> get order;
|
||||||
@ -861,6 +871,8 @@ class _$PaymentFormStateCopyWithImpl<$Res, $Val extends PaymentFormState>
|
|||||||
Object? failureOrPaymentSplitBill = null,
|
Object? failureOrPaymentSplitBill = null,
|
||||||
Object? paymentMethod = freezed,
|
Object? paymentMethod = freezed,
|
||||||
Object? isSubmitting = null,
|
Object? isSubmitting = null,
|
||||||
|
Object? idempotencyKey = freezed,
|
||||||
|
Object? splitBillIdempotencyKey = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@ -888,6 +900,14 @@ class _$PaymentFormStateCopyWithImpl<$Res, $Val extends PaymentFormState>
|
|||||||
? _value.isSubmitting
|
? _value.isSubmitting
|
||||||
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
idempotencyKey: freezed == idempotencyKey
|
||||||
|
? _value.idempotencyKey
|
||||||
|
: idempotencyKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
splitBillIdempotencyKey: freezed == splitBillIdempotencyKey
|
||||||
|
? _value.splitBillIdempotencyKey
|
||||||
|
: splitBillIdempotencyKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@ -934,6 +954,8 @@ abstract class _$$PaymentFormStateImplCopyWith<$Res>
|
|||||||
Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
|
Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
|
||||||
PaymentMethod? paymentMethod,
|
PaymentMethod? paymentMethod,
|
||||||
bool isSubmitting,
|
bool isSubmitting,
|
||||||
|
String? idempotencyKey,
|
||||||
|
String? splitBillIdempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -962,6 +984,8 @@ class __$$PaymentFormStateImplCopyWithImpl<$Res>
|
|||||||
Object? failureOrPaymentSplitBill = null,
|
Object? failureOrPaymentSplitBill = null,
|
||||||
Object? paymentMethod = freezed,
|
Object? paymentMethod = freezed,
|
||||||
Object? isSubmitting = null,
|
Object? isSubmitting = null,
|
||||||
|
Object? idempotencyKey = freezed,
|
||||||
|
Object? splitBillIdempotencyKey = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$PaymentFormStateImpl(
|
_$PaymentFormStateImpl(
|
||||||
@ -989,6 +1013,14 @@ class __$$PaymentFormStateImplCopyWithImpl<$Res>
|
|||||||
? _value.isSubmitting
|
? _value.isSubmitting
|
||||||
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
idempotencyKey: freezed == idempotencyKey
|
||||||
|
? _value.idempotencyKey
|
||||||
|
: idempotencyKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
splitBillIdempotencyKey: freezed == splitBillIdempotencyKey
|
||||||
|
? _value.splitBillIdempotencyKey
|
||||||
|
: splitBillIdempotencyKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1004,6 +1036,8 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
|
|||||||
required this.failureOrPaymentSplitBill,
|
required this.failureOrPaymentSplitBill,
|
||||||
this.paymentMethod,
|
this.paymentMethod,
|
||||||
this.isSubmitting = false,
|
this.isSubmitting = false,
|
||||||
|
this.idempotencyKey,
|
||||||
|
this.splitBillIdempotencyKey,
|
||||||
}) : _pendingItems = pendingItems;
|
}) : _pendingItems = pendingItems;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1026,9 +1060,19 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
|
|||||||
@JsonKey()
|
@JsonKey()
|
||||||
final bool isSubmitting;
|
final bool isSubmitting;
|
||||||
|
|
||||||
|
/// Idempotency key untuk payment submission.
|
||||||
|
/// Di-generate saat user tap "Bayar", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses.
|
||||||
|
@override
|
||||||
|
final String? idempotencyKey;
|
||||||
|
|
||||||
|
/// Idempotency key untuk split bill submission.
|
||||||
|
@override
|
||||||
|
final String? splitBillIdempotencyKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'PaymentFormState(order: $order, pendingItems: $pendingItems, failureOrPayment: $failureOrPayment, failureOrPaymentSplitBill: $failureOrPaymentSplitBill, paymentMethod: $paymentMethod, isSubmitting: $isSubmitting)';
|
return 'PaymentFormState(order: $order, pendingItems: $pendingItems, failureOrPayment: $failureOrPayment, failureOrPaymentSplitBill: $failureOrPaymentSplitBill, paymentMethod: $paymentMethod, isSubmitting: $isSubmitting, idempotencyKey: $idempotencyKey, splitBillIdempotencyKey: $splitBillIdempotencyKey)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1051,7 +1095,14 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
|
|||||||
(identical(other.paymentMethod, paymentMethod) ||
|
(identical(other.paymentMethod, paymentMethod) ||
|
||||||
other.paymentMethod == paymentMethod) &&
|
other.paymentMethod == paymentMethod) &&
|
||||||
(identical(other.isSubmitting, isSubmitting) ||
|
(identical(other.isSubmitting, isSubmitting) ||
|
||||||
other.isSubmitting == isSubmitting));
|
other.isSubmitting == isSubmitting) &&
|
||||||
|
(identical(other.idempotencyKey, idempotencyKey) ||
|
||||||
|
other.idempotencyKey == idempotencyKey) &&
|
||||||
|
(identical(
|
||||||
|
other.splitBillIdempotencyKey,
|
||||||
|
splitBillIdempotencyKey,
|
||||||
|
) ||
|
||||||
|
other.splitBillIdempotencyKey == splitBillIdempotencyKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1063,6 +1114,8 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
|
|||||||
failureOrPaymentSplitBill,
|
failureOrPaymentSplitBill,
|
||||||
paymentMethod,
|
paymentMethod,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
|
idempotencyKey,
|
||||||
|
splitBillIdempotencyKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Create a copy of PaymentFormState
|
/// Create a copy of PaymentFormState
|
||||||
@ -1086,6 +1139,8 @@ abstract class _PaymentFormState implements PaymentFormState {
|
|||||||
failureOrPaymentSplitBill,
|
failureOrPaymentSplitBill,
|
||||||
final PaymentMethod? paymentMethod,
|
final PaymentMethod? paymentMethod,
|
||||||
final bool isSubmitting,
|
final bool isSubmitting,
|
||||||
|
final String? idempotencyKey,
|
||||||
|
final String? splitBillIdempotencyKey,
|
||||||
}) = _$PaymentFormStateImpl;
|
}) = _$PaymentFormStateImpl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1101,6 +1156,16 @@ abstract class _PaymentFormState implements PaymentFormState {
|
|||||||
@override
|
@override
|
||||||
bool get isSubmitting;
|
bool get isSubmitting;
|
||||||
|
|
||||||
|
/// Idempotency key untuk payment submission.
|
||||||
|
/// Di-generate saat user tap "Bayar", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses.
|
||||||
|
@override
|
||||||
|
String? get idempotencyKey;
|
||||||
|
|
||||||
|
/// Idempotency key untuk split bill submission.
|
||||||
|
@override
|
||||||
|
String? get splitBillIdempotencyKey;
|
||||||
|
|
||||||
/// Create a copy of PaymentFormState
|
/// Create a copy of PaymentFormState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -9,6 +9,12 @@ class PaymentFormState with _$PaymentFormState {
|
|||||||
required Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
|
required Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
|
||||||
PaymentMethod? paymentMethod,
|
PaymentMethod? paymentMethod,
|
||||||
@Default(false) bool isSubmitting,
|
@Default(false) bool isSubmitting,
|
||||||
|
/// Idempotency key untuk payment submission.
|
||||||
|
/// Di-generate saat user tap "Bayar", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses.
|
||||||
|
String? idempotencyKey,
|
||||||
|
/// Idempotency key untuk split bill submission.
|
||||||
|
String? splitBillIdempotencyKey,
|
||||||
}) = _PaymentFormState;
|
}) = _PaymentFormState;
|
||||||
|
|
||||||
factory PaymentFormState.initial() => PaymentFormState(
|
factory PaymentFormState.initial() => PaymentFormState(
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart';
|
|||||||
import 'package:dartz/dartz.dart' hide Order;
|
import 'package:dartz/dartz.dart' hide Order;
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:injectable/injectable.dart' hide Order;
|
import 'package:injectable/injectable.dart' hide Order;
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../../../common/data/refund_data.dart';
|
import '../../../common/data/refund_data.dart';
|
||||||
import '../../../domain/order/order.dart';
|
import '../../../domain/order/order.dart';
|
||||||
@ -34,7 +35,15 @@ class RefundFormBloc extends Bloc<RefundFormEvent, RefundFormState> {
|
|||||||
submitted: (e) async {
|
submitted: (e) async {
|
||||||
Either<OrderFailure, Unit>? failureOrRefund;
|
Either<OrderFailure, Unit>? failureOrRefund;
|
||||||
|
|
||||||
emit(state.copyWith(isSubmitting: true, failureOrRefund: none()));
|
// Generate new idempotency key saat user intent (tap Refund)
|
||||||
|
// Kalau sedang retry, pakai key yang sama
|
||||||
|
final idempotencyKey = state.idempotencyKey ?? const Uuid().v4();
|
||||||
|
|
||||||
|
emit(state.copyWith(
|
||||||
|
isSubmitting: true,
|
||||||
|
failureOrRefund: none(),
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
|
));
|
||||||
|
|
||||||
failureOrRefund = await _repository.refundOrder(
|
failureOrRefund = await _repository.refundOrder(
|
||||||
id: state.order.id,
|
id: state.order.id,
|
||||||
@ -42,12 +51,15 @@ class RefundFormBloc extends Bloc<RefundFormEvent, RefundFormState> {
|
|||||||
? state.reason
|
? state.reason
|
||||||
: state.refundReason?.value ?? '',
|
: state.refundReason?.value ?? '',
|
||||||
refundAmount: state.order.totalAmount,
|
refundAmount: state.order.totalAmount,
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
failureOrRefund: optionOf(failureOrRefund),
|
failureOrRefund: optionOf(failureOrRefund),
|
||||||
|
// Clear key on success, keep on failure for retry
|
||||||
|
idempotencyKey: failureOrRefund.isRight() ? null : idempotencyKey,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -692,6 +692,11 @@ mixin _$RefundFormState {
|
|||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
bool get isSubmitting => throw _privateConstructorUsedError;
|
bool get isSubmitting => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Idempotency key untuk refund submission.
|
||||||
|
/// Di-generate saat user tap "Refund", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses.
|
||||||
|
String? get idempotencyKey => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Create a copy of RefundFormState
|
/// Create a copy of RefundFormState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -712,6 +717,7 @@ abstract class $RefundFormStateCopyWith<$Res> {
|
|||||||
RefundReason? refundReason,
|
RefundReason? refundReason,
|
||||||
Option<Either<OrderFailure, Unit>> failureOrRefund,
|
Option<Either<OrderFailure, Unit>> failureOrRefund,
|
||||||
bool isSubmitting,
|
bool isSubmitting,
|
||||||
|
String? idempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
$OrderCopyWith<$Res> get order;
|
$OrderCopyWith<$Res> get order;
|
||||||
@ -737,6 +743,7 @@ class _$RefundFormStateCopyWithImpl<$Res, $Val extends RefundFormState>
|
|||||||
Object? refundReason = freezed,
|
Object? refundReason = freezed,
|
||||||
Object? failureOrRefund = null,
|
Object? failureOrRefund = null,
|
||||||
Object? isSubmitting = null,
|
Object? isSubmitting = null,
|
||||||
|
Object? idempotencyKey = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@ -760,6 +767,10 @@ class _$RefundFormStateCopyWithImpl<$Res, $Val extends RefundFormState>
|
|||||||
? _value.isSubmitting
|
? _value.isSubmitting
|
||||||
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
idempotencyKey: freezed == idempotencyKey
|
||||||
|
? _value.idempotencyKey
|
||||||
|
: idempotencyKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@ -791,6 +802,7 @@ abstract class _$$RefundFormStateImplCopyWith<$Res>
|
|||||||
RefundReason? refundReason,
|
RefundReason? refundReason,
|
||||||
Option<Either<OrderFailure, Unit>> failureOrRefund,
|
Option<Either<OrderFailure, Unit>> failureOrRefund,
|
||||||
bool isSubmitting,
|
bool isSubmitting,
|
||||||
|
String? idempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -816,6 +828,7 @@ class __$$RefundFormStateImplCopyWithImpl<$Res>
|
|||||||
Object? refundReason = freezed,
|
Object? refundReason = freezed,
|
||||||
Object? failureOrRefund = null,
|
Object? failureOrRefund = null,
|
||||||
Object? isSubmitting = null,
|
Object? isSubmitting = null,
|
||||||
|
Object? idempotencyKey = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$RefundFormStateImpl(
|
_$RefundFormStateImpl(
|
||||||
@ -839,6 +852,10 @@ class __$$RefundFormStateImplCopyWithImpl<$Res>
|
|||||||
? _value.isSubmitting
|
? _value.isSubmitting
|
||||||
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
idempotencyKey: freezed == idempotencyKey
|
||||||
|
? _value.idempotencyKey
|
||||||
|
: idempotencyKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -853,6 +870,7 @@ class _$RefundFormStateImpl implements _RefundFormState {
|
|||||||
this.refundReason,
|
this.refundReason,
|
||||||
required this.failureOrRefund,
|
required this.failureOrRefund,
|
||||||
this.isSubmitting = false,
|
this.isSubmitting = false,
|
||||||
|
this.idempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -867,9 +885,15 @@ class _$RefundFormStateImpl implements _RefundFormState {
|
|||||||
@JsonKey()
|
@JsonKey()
|
||||||
final bool isSubmitting;
|
final bool isSubmitting;
|
||||||
|
|
||||||
|
/// Idempotency key untuk refund submission.
|
||||||
|
/// Di-generate saat user tap "Refund", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses.
|
||||||
|
@override
|
||||||
|
final String? idempotencyKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'RefundFormState(order: $order, reason: $reason, refundReason: $refundReason, failureOrRefund: $failureOrRefund, isSubmitting: $isSubmitting)';
|
return 'RefundFormState(order: $order, reason: $reason, refundReason: $refundReason, failureOrRefund: $failureOrRefund, isSubmitting: $isSubmitting, idempotencyKey: $idempotencyKey)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -884,7 +908,9 @@ class _$RefundFormStateImpl implements _RefundFormState {
|
|||||||
(identical(other.failureOrRefund, failureOrRefund) ||
|
(identical(other.failureOrRefund, failureOrRefund) ||
|
||||||
other.failureOrRefund == failureOrRefund) &&
|
other.failureOrRefund == failureOrRefund) &&
|
||||||
(identical(other.isSubmitting, isSubmitting) ||
|
(identical(other.isSubmitting, isSubmitting) ||
|
||||||
other.isSubmitting == isSubmitting));
|
other.isSubmitting == isSubmitting) &&
|
||||||
|
(identical(other.idempotencyKey, idempotencyKey) ||
|
||||||
|
other.idempotencyKey == idempotencyKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -895,6 +921,7 @@ class _$RefundFormStateImpl implements _RefundFormState {
|
|||||||
refundReason,
|
refundReason,
|
||||||
failureOrRefund,
|
failureOrRefund,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
|
idempotencyKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Create a copy of RefundFormState
|
/// Create a copy of RefundFormState
|
||||||
@ -916,6 +943,7 @@ abstract class _RefundFormState implements RefundFormState {
|
|||||||
final RefundReason? refundReason,
|
final RefundReason? refundReason,
|
||||||
required final Option<Either<OrderFailure, Unit>> failureOrRefund,
|
required final Option<Either<OrderFailure, Unit>> failureOrRefund,
|
||||||
final bool isSubmitting,
|
final bool isSubmitting,
|
||||||
|
final String? idempotencyKey,
|
||||||
}) = _$RefundFormStateImpl;
|
}) = _$RefundFormStateImpl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -929,6 +957,12 @@ abstract class _RefundFormState implements RefundFormState {
|
|||||||
@override
|
@override
|
||||||
bool get isSubmitting;
|
bool get isSubmitting;
|
||||||
|
|
||||||
|
/// Idempotency key untuk refund submission.
|
||||||
|
/// Di-generate saat user tap "Refund", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses.
|
||||||
|
@override
|
||||||
|
String? get idempotencyKey;
|
||||||
|
|
||||||
/// Create a copy of RefundFormState
|
/// Create a copy of RefundFormState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -8,6 +8,10 @@ class RefundFormState with _$RefundFormState {
|
|||||||
RefundReason? refundReason,
|
RefundReason? refundReason,
|
||||||
required Option<Either<OrderFailure, Unit>> failureOrRefund,
|
required Option<Either<OrderFailure, Unit>> failureOrRefund,
|
||||||
@Default(false) bool isSubmitting,
|
@Default(false) bool isSubmitting,
|
||||||
|
/// Idempotency key untuk refund submission.
|
||||||
|
/// Di-generate saat user tap "Refund", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses.
|
||||||
|
String? idempotencyKey,
|
||||||
}) = _RefundFormState;
|
}) = _RefundFormState;
|
||||||
|
|
||||||
factory RefundFormState.initial() => RefundFormState(
|
factory RefundFormState.initial() => RefundFormState(
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:dartz/dartz.dart' hide Order;
|
import 'package:dartz/dartz.dart' hide Order;
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:injectable/injectable.dart' hide Order;
|
import 'package:injectable/injectable.dart' hide Order;
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../../../common/types/void_type.dart';
|
import '../../../common/types/void_type.dart';
|
||||||
import '../../../domain/order/order.dart';
|
import '../../../domain/order/order.dart';
|
||||||
@ -105,13 +106,22 @@ class VoidFormBloc extends Bloc<VoidFormEvent, VoidFormState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(isSubmitting: true, failureOrVoid: none()));
|
// Generate new idempotency key saat user intent (tap Void)
|
||||||
|
// Kalau sedang retry, pakai key yang sama
|
||||||
|
final idempotencyKey = state.idempotencyKey ?? const Uuid().v4();
|
||||||
|
|
||||||
|
emit(state.copyWith(
|
||||||
|
isSubmitting: true,
|
||||||
|
failureOrVoid: none(),
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
|
));
|
||||||
|
|
||||||
failureOrVoid = await _repository.voidOrder(
|
failureOrVoid = await _repository.voidOrder(
|
||||||
orderId: state.order.id,
|
orderId: state.order.id,
|
||||||
reason: state.voidReason ?? '',
|
reason: state.voidReason ?? '',
|
||||||
orderItems: voidItems,
|
orderItems: voidItems,
|
||||||
type: state.voidType.toStringType(),
|
type: state.voidType.toStringType(),
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
@ -119,6 +129,8 @@ class VoidFormBloc extends Bloc<VoidFormEvent, VoidFormState> {
|
|||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
failureOrVoid: optionOf(failureOrVoid),
|
failureOrVoid: optionOf(failureOrVoid),
|
||||||
voidItems: state.voidType.isItem ? voidItems : state.pendingItems,
|
voidItems: state.voidType.isItem ? voidItems : state.pendingItems,
|
||||||
|
// Clear key on success, keep on failure for retry
|
||||||
|
idempotencyKey: failureOrVoid.isRight() ? null : idempotencyKey,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1256,6 +1256,11 @@ mixin _$VoidFormState {
|
|||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
bool get isSubmitting => throw _privateConstructorUsedError;
|
bool get isSubmitting => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Idempotency key untuk void submission.
|
||||||
|
/// Di-generate saat user tap "Void", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses atau clearState.
|
||||||
|
String? get idempotencyKey => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Create a copy of VoidFormState
|
/// Create a copy of VoidFormState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -1280,6 +1285,7 @@ abstract class $VoidFormStateCopyWith<$Res> {
|
|||||||
int totalPriceVoid,
|
int totalPriceVoid,
|
||||||
Option<Either<OrderFailure, Unit>> failureOrVoid,
|
Option<Either<OrderFailure, Unit>> failureOrVoid,
|
||||||
bool isSubmitting,
|
bool isSubmitting,
|
||||||
|
String? idempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
$OrderCopyWith<$Res> get order;
|
$OrderCopyWith<$Res> get order;
|
||||||
@ -1309,6 +1315,7 @@ class _$VoidFormStateCopyWithImpl<$Res, $Val extends VoidFormState>
|
|||||||
Object? totalPriceVoid = null,
|
Object? totalPriceVoid = null,
|
||||||
Object? failureOrVoid = null,
|
Object? failureOrVoid = null,
|
||||||
Object? isSubmitting = null,
|
Object? isSubmitting = null,
|
||||||
|
Object? idempotencyKey = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_value.copyWith(
|
_value.copyWith(
|
||||||
@ -1348,6 +1355,10 @@ class _$VoidFormStateCopyWithImpl<$Res, $Val extends VoidFormState>
|
|||||||
? _value.isSubmitting
|
? _value.isSubmitting
|
||||||
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
idempotencyKey: freezed == idempotencyKey
|
||||||
|
? _value.idempotencyKey
|
||||||
|
: idempotencyKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
)
|
)
|
||||||
as $Val,
|
as $Val,
|
||||||
);
|
);
|
||||||
@ -1383,6 +1394,7 @@ abstract class _$$VoidFormStateImplCopyWith<$Res>
|
|||||||
int totalPriceVoid,
|
int totalPriceVoid,
|
||||||
Option<Either<OrderFailure, Unit>> failureOrVoid,
|
Option<Either<OrderFailure, Unit>> failureOrVoid,
|
||||||
bool isSubmitting,
|
bool isSubmitting,
|
||||||
|
String? idempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1412,6 +1424,7 @@ class __$$VoidFormStateImplCopyWithImpl<$Res>
|
|||||||
Object? totalPriceVoid = null,
|
Object? totalPriceVoid = null,
|
||||||
Object? failureOrVoid = null,
|
Object? failureOrVoid = null,
|
||||||
Object? isSubmitting = null,
|
Object? isSubmitting = null,
|
||||||
|
Object? idempotencyKey = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(
|
return _then(
|
||||||
_$VoidFormStateImpl(
|
_$VoidFormStateImpl(
|
||||||
@ -1451,6 +1464,10 @@ class __$$VoidFormStateImplCopyWithImpl<$Res>
|
|||||||
? _value.isSubmitting
|
? _value.isSubmitting
|
||||||
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
: isSubmitting // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
idempotencyKey: freezed == idempotencyKey
|
||||||
|
? _value.idempotencyKey
|
||||||
|
: idempotencyKey // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1469,6 +1486,7 @@ class _$VoidFormStateImpl implements _VoidFormState {
|
|||||||
required this.totalPriceVoid,
|
required this.totalPriceVoid,
|
||||||
required this.failureOrVoid,
|
required this.failureOrVoid,
|
||||||
this.isSubmitting = false,
|
this.isSubmitting = false,
|
||||||
|
this.idempotencyKey,
|
||||||
}) : _pendingItems = pendingItems,
|
}) : _pendingItems = pendingItems,
|
||||||
_voidItems = voidItems,
|
_voidItems = voidItems,
|
||||||
_selectedItemQuantities = selectedItemQuantities;
|
_selectedItemQuantities = selectedItemQuantities;
|
||||||
@ -1512,9 +1530,15 @@ class _$VoidFormStateImpl implements _VoidFormState {
|
|||||||
@JsonKey()
|
@JsonKey()
|
||||||
final bool isSubmitting;
|
final bool isSubmitting;
|
||||||
|
|
||||||
|
/// Idempotency key untuk void submission.
|
||||||
|
/// Di-generate saat user tap "Void", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses atau clearState.
|
||||||
|
@override
|
||||||
|
final String? idempotencyKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'VoidFormState(order: $order, pendingItems: $pendingItems, voidItems: $voidItems, voidType: $voidType, selectedItemQuantities: $selectedItemQuantities, voidReason: $voidReason, totalPriceVoid: $totalPriceVoid, failureOrVoid: $failureOrVoid, isSubmitting: $isSubmitting)';
|
return 'VoidFormState(order: $order, pendingItems: $pendingItems, voidItems: $voidItems, voidType: $voidType, selectedItemQuantities: $selectedItemQuantities, voidReason: $voidReason, totalPriceVoid: $totalPriceVoid, failureOrVoid: $failureOrVoid, isSubmitting: $isSubmitting, idempotencyKey: $idempotencyKey)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1544,7 +1568,9 @@ class _$VoidFormStateImpl implements _VoidFormState {
|
|||||||
(identical(other.failureOrVoid, failureOrVoid) ||
|
(identical(other.failureOrVoid, failureOrVoid) ||
|
||||||
other.failureOrVoid == failureOrVoid) &&
|
other.failureOrVoid == failureOrVoid) &&
|
||||||
(identical(other.isSubmitting, isSubmitting) ||
|
(identical(other.isSubmitting, isSubmitting) ||
|
||||||
other.isSubmitting == isSubmitting));
|
other.isSubmitting == isSubmitting) &&
|
||||||
|
(identical(other.idempotencyKey, idempotencyKey) ||
|
||||||
|
other.idempotencyKey == idempotencyKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1559,6 +1585,7 @@ class _$VoidFormStateImpl implements _VoidFormState {
|
|||||||
totalPriceVoid,
|
totalPriceVoid,
|
||||||
failureOrVoid,
|
failureOrVoid,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
|
idempotencyKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Create a copy of VoidFormState
|
/// Create a copy of VoidFormState
|
||||||
@ -1581,6 +1608,7 @@ abstract class _VoidFormState implements VoidFormState {
|
|||||||
required final int totalPriceVoid,
|
required final int totalPriceVoid,
|
||||||
required final Option<Either<OrderFailure, Unit>> failureOrVoid,
|
required final Option<Either<OrderFailure, Unit>> failureOrVoid,
|
||||||
final bool isSubmitting,
|
final bool isSubmitting,
|
||||||
|
final String? idempotencyKey,
|
||||||
}) = _$VoidFormStateImpl;
|
}) = _$VoidFormStateImpl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1602,6 +1630,12 @@ abstract class _VoidFormState implements VoidFormState {
|
|||||||
@override
|
@override
|
||||||
bool get isSubmitting;
|
bool get isSubmitting;
|
||||||
|
|
||||||
|
/// Idempotency key untuk void submission.
|
||||||
|
/// Di-generate saat user tap "Void", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses atau clearState.
|
||||||
|
@override
|
||||||
|
String? get idempotencyKey;
|
||||||
|
|
||||||
/// Create a copy of VoidFormState
|
/// Create a copy of VoidFormState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -12,6 +12,10 @@ class VoidFormState with _$VoidFormState {
|
|||||||
required int totalPriceVoid,
|
required int totalPriceVoid,
|
||||||
required Option<Either<OrderFailure, Unit>> failureOrVoid,
|
required Option<Either<OrderFailure, Unit>> failureOrVoid,
|
||||||
@Default(false) bool isSubmitting,
|
@Default(false) bool isSubmitting,
|
||||||
|
/// Idempotency key untuk void submission.
|
||||||
|
/// Di-generate saat user tap "Void", dipakai ulang saat retry,
|
||||||
|
/// di-clear setelah sukses atau clearState.
|
||||||
|
String? idempotencyKey,
|
||||||
}) = _VoidFormState;
|
}) = _VoidFormState;
|
||||||
|
|
||||||
factory VoidFormState.initial() => VoidFormState(
|
factory VoidFormState.initial() => VoidFormState(
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import 'interceptors/bad_network_interceptor.dart';
|
|||||||
import 'interceptors/bad_request_interceptor.dart';
|
import 'interceptors/bad_request_interceptor.dart';
|
||||||
import 'interceptors/connection_timeout_interceptor.dart';
|
import 'interceptors/connection_timeout_interceptor.dart';
|
||||||
import 'interceptors/crashlytic_interceptor.dart';
|
import 'interceptors/crashlytic_interceptor.dart';
|
||||||
|
import 'interceptors/idempotency_interceptor.dart';
|
||||||
import 'interceptors/internal_server_interceptor.dart';
|
import 'interceptors/internal_server_interceptor.dart';
|
||||||
import 'interceptors/not_found_interceptor.dart';
|
import 'interceptors/not_found_interceptor.dart';
|
||||||
import 'interceptors/unauthorized_interceptor.dart';
|
import 'interceptors/unauthorized_interceptor.dart';
|
||||||
@ -30,6 +31,7 @@ class ApiClient {
|
|||||||
_dio.options.connectTimeout = const Duration(seconds: 20);
|
_dio.options.connectTimeout = const Duration(seconds: 20);
|
||||||
_dio.options.validateStatus = (status) =>
|
_dio.options.validateStatus = (status) =>
|
||||||
status != null && status >= 200 && status < 500;
|
status != null && status >= 200 && status < 500;
|
||||||
|
_dio.interceptors.add(IdempotencyInterceptor(_dio));
|
||||||
_dio.interceptors.add(BadNetworkErrorInterceptor());
|
_dio.interceptors.add(BadNetworkErrorInterceptor());
|
||||||
_dio.interceptors.add(BadRequestErrorInterceptor());
|
_dio.interceptors.add(BadRequestErrorInterceptor());
|
||||||
_dio.interceptors.add(InternalServerErrorInterceptor());
|
_dio.interceptors.add(InternalServerErrorInterceptor());
|
||||||
|
|||||||
104
lib/common/api/interceptors/idempotency_interceptor.dart
Normal file
104
lib/common/api/interceptors/idempotency_interceptor.dart
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
/// Interceptor yang secara otomatis menambahkan header X-Idempotency-Key
|
||||||
|
/// untuk endpoint-endpoint kritis yang memerlukan idempotency.
|
||||||
|
///
|
||||||
|
/// Key sebaiknya di-generate di level BLoC/Cubit saat user trigger action,
|
||||||
|
/// lalu dikirim via headers. Interceptor ini hanya berfungsi sebagai fallback
|
||||||
|
/// jika key belum di-set.
|
||||||
|
///
|
||||||
|
/// Jika response 409 dengan error code `request_in_progress`, interceptor
|
||||||
|
/// akan retry setelah delay 1-2 detik.
|
||||||
|
class IdempotencyInterceptor extends Interceptor {
|
||||||
|
static const _headerKey = 'X-Idempotency-Key';
|
||||||
|
static const _replayHeader = 'X-Idempotent-Replay';
|
||||||
|
|
||||||
|
// Endpoints that require idempotency keys (exact match)
|
||||||
|
static const _idempotentPaths = [
|
||||||
|
'/api/v1/payments',
|
||||||
|
'/api/v1/orders/void',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Endpoints that require idempotency keys (pattern match)
|
||||||
|
static final _idempotentPathPatterns = [
|
||||||
|
RegExp(r'/api/v1/orders/.+/add-items$'),
|
||||||
|
RegExp(r'/api/v1/orders/.+/refund$'),
|
||||||
|
RegExp(r'/api/v1/payments/.+/refund$'),
|
||||||
|
];
|
||||||
|
|
||||||
|
static const _maxRetries = 3;
|
||||||
|
static const _retryDelay = Duration(seconds: 2);
|
||||||
|
|
||||||
|
final Dio _dio;
|
||||||
|
|
||||||
|
IdempotencyInterceptor(this._dio);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||||
|
if (options.method == 'POST' && _requiresIdempotencyKey(options.path)) {
|
||||||
|
// Gunakan key yang sudah di-set dari BLoC, atau generate baru sebagai fallback
|
||||||
|
options.headers[_headerKey] ??= const Uuid().v4();
|
||||||
|
}
|
||||||
|
handler.next(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||||
|
// Log jika response adalah replay dari request sebelumnya
|
||||||
|
final isReplay = response.headers.value(_replayHeader);
|
||||||
|
if (isReplay == 'true') {
|
||||||
|
// Response ini adalah hasil dari request pertama yang sudah diproses.
|
||||||
|
// Treat as success — tidak perlu perlakuan khusus.
|
||||||
|
}
|
||||||
|
handler.next(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||||
|
final response = err.response;
|
||||||
|
final options = err.requestOptions;
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
handler.next(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle 409 Conflict with request_in_progress
|
||||||
|
if (response.statusCode == 409 &&
|
||||||
|
_isRequestInProgress(response.data) &&
|
||||||
|
_requiresIdempotencyKey(options.path)) {
|
||||||
|
final retryCount = options.extra['_idempotency_retry_count'] ?? 0;
|
||||||
|
|
||||||
|
if (retryCount < _maxRetries) {
|
||||||
|
await Future.delayed(_retryDelay);
|
||||||
|
|
||||||
|
options.extra['_idempotency_retry_count'] = retryCount + 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final retryResponse = await _dio.fetch(options);
|
||||||
|
handler.resolve(retryResponse);
|
||||||
|
} on DioException catch (e) {
|
||||||
|
handler.next(e);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _requiresIdempotencyKey(String path) {
|
||||||
|
if (_idempotentPaths.any((p) => path.endsWith(p))) return true;
|
||||||
|
if (_idempotentPathPatterns.any((r) => r.hasMatch(path))) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isRequestInProgress(dynamic data) {
|
||||||
|
if (data is Map<String, dynamic>) {
|
||||||
|
final errorCode = data['error_code'] ?? data['code'] ?? '';
|
||||||
|
return errorCode == 'request_in_progress';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,7 +17,7 @@ class DatabaseHelper {
|
|||||||
|
|
||||||
return await openDatabase(
|
return await openDatabase(
|
||||||
path,
|
path,
|
||||||
version: 1, // Updated version for categories table
|
version: 2,
|
||||||
onCreate: _onCreate,
|
onCreate: _onCreate,
|
||||||
onUpgrade: _onUpgrade,
|
onUpgrade: _onUpgrade,
|
||||||
);
|
);
|
||||||
@ -38,6 +38,7 @@ class DatabaseHelper {
|
|||||||
business_type TEXT,
|
business_type TEXT,
|
||||||
image_url TEXT,
|
image_url TEXT,
|
||||||
printer_type TEXT,
|
printer_type TEXT,
|
||||||
|
print_to_checker INTEGER DEFAULT 0,
|
||||||
metadata TEXT,
|
metadata TEXT,
|
||||||
is_active INTEGER,
|
is_active INTEGER,
|
||||||
created_at TEXT,
|
created_at TEXT,
|
||||||
@ -107,7 +108,13 @@ class DatabaseHelper {
|
|||||||
await db.execute('CREATE INDEX idx_printers_type ON printers(type)');
|
await db.execute('CREATE INDEX idx_printers_type ON printers(type)');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {}
|
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
|
||||||
|
if (oldVersion < 2) {
|
||||||
|
await db.execute(
|
||||||
|
'ALTER TABLE products ADD COLUMN print_to_checker INTEGER DEFAULT 0',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
|
|||||||
@ -24,10 +24,12 @@ abstract class IOrderRepository {
|
|||||||
Future<Either<OrderFailure, Order>> addItemOrder({
|
Future<Either<OrderFailure, Order>> addItemOrder({
|
||||||
required String id,
|
required String id,
|
||||||
required List<AddItemOrderRequest> request,
|
required List<AddItemOrderRequest> request,
|
||||||
|
String? idempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Either<OrderFailure, Payment>> createPayment({
|
Future<Either<OrderFailure, Payment>> createPayment({
|
||||||
required PaymentRequest request,
|
required PaymentRequest request,
|
||||||
|
String? idempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Either<OrderFailure, Unit>> voidOrder({
|
Future<Either<OrderFailure, Unit>> voidOrder({
|
||||||
@ -35,15 +37,18 @@ abstract class IOrderRepository {
|
|||||||
required String reason,
|
required String reason,
|
||||||
String type = "ITEM", // TYPE: ALL, ITEM
|
String type = "ITEM", // TYPE: ALL, ITEM
|
||||||
required List<OrderItem> orderItems,
|
required List<OrderItem> orderItems,
|
||||||
|
String? idempotencyKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Either<OrderFailure, Payment>> createSplitBill(
|
Future<Either<OrderFailure, Payment>> createSplitBill(
|
||||||
PaymentSplitBillRequest request,
|
PaymentSplitBillRequest request, {
|
||||||
);
|
String? idempotencyKey,
|
||||||
|
});
|
||||||
|
|
||||||
Future<Either<OrderFailure, Unit>> refundOrder({
|
Future<Either<OrderFailure, Unit>> refundOrder({
|
||||||
required String id,
|
required String id,
|
||||||
required String reason,
|
required String reason,
|
||||||
required int refundAmount,
|
required int refundAmount,
|
||||||
|
String? idempotencyKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,15 @@ class OrderRemoteDataProvider {
|
|||||||
final _logName = 'OrderRemoteDataProvider';
|
final _logName = 'OrderRemoteDataProvider';
|
||||||
OrderRemoteDataProvider(this._apiClient);
|
OrderRemoteDataProvider(this._apiClient);
|
||||||
|
|
||||||
|
/// Helper untuk merge auth header dengan idempotency key
|
||||||
|
Map<String, dynamic> _buildHeaders({String? idempotencyKey}) {
|
||||||
|
final headers = getAuthorizationHeader();
|
||||||
|
if (idempotencyKey != null) {
|
||||||
|
headers['X-Idempotency-Key'] = idempotencyKey;
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
Future<DC<OrderFailure, ListOrderDto>> fetchOrders({
|
Future<DC<OrderFailure, ListOrderDto>> fetchOrders({
|
||||||
int page = 1,
|
int page = 1,
|
||||||
int limit = 10,
|
int limit = 10,
|
||||||
@ -151,13 +160,14 @@ class OrderRemoteDataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<DC<OrderFailure, PaymentDto>> storePayment(
|
Future<DC<OrderFailure, PaymentDto>> storePayment(
|
||||||
PaymentRequestDto request,
|
PaymentRequestDto request, {
|
||||||
) async {
|
String? idempotencyKey,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await _apiClient.post(
|
final response = await _apiClient.post(
|
||||||
ApiPath.payments,
|
ApiPath.payments,
|
||||||
data: request.toJson(),
|
data: request.toJson(),
|
||||||
headers: getAuthorizationHeader(),
|
headers: _buildHeaders(idempotencyKey: idempotencyKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
if (response.data['success'] == false) {
|
||||||
@ -178,6 +188,7 @@ class OrderRemoteDataProvider {
|
|||||||
Future<DC<OrderFailure, OrderDto>> addItemOrder({
|
Future<DC<OrderFailure, OrderDto>> addItemOrder({
|
||||||
required String id,
|
required String id,
|
||||||
required List<AddItemOrderRequestDto> request,
|
required List<AddItemOrderRequestDto> request,
|
||||||
|
String? idempotencyKey,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await _apiClient.post(
|
final response = await _apiClient.post(
|
||||||
@ -186,7 +197,7 @@ class OrderRemoteDataProvider {
|
|||||||
'notes': '',
|
'notes': '',
|
||||||
'order_items': request.map((e) => e.toRequest()).toList(),
|
'order_items': request.map((e) => e.toRequest()).toList(),
|
||||||
},
|
},
|
||||||
headers: getAuthorizationHeader(),
|
headers: _buildHeaders(idempotencyKey: idempotencyKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
if (response.data['success'] == false) {
|
||||||
@ -209,6 +220,7 @@ class OrderRemoteDataProvider {
|
|||||||
required String reason,
|
required String reason,
|
||||||
String type = "ITEM", // TYPE: ALL, ITEM
|
String type = "ITEM", // TYPE: ALL, ITEM
|
||||||
required List<OrderItemDto> orderItems,
|
required List<OrderItemDto> orderItems,
|
||||||
|
String? idempotencyKey,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await _apiClient.post(
|
final response = await _apiClient.post(
|
||||||
@ -223,7 +235,7 @@ class OrderRemoteDataProvider {
|
|||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
},
|
},
|
||||||
headers: getAuthorizationHeader(),
|
headers: _buildHeaders(idempotencyKey: idempotencyKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
if (response.data['success'] == false) {
|
||||||
@ -238,14 +250,15 @@ class OrderRemoteDataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<DC<OrderFailure, PaymentDto>> createSplitBill(
|
Future<DC<OrderFailure, PaymentDto>> createSplitBill(
|
||||||
PaymentSplitBillRequestDto request,
|
PaymentSplitBillRequestDto request, {
|
||||||
) async {
|
String? idempotencyKey,
|
||||||
|
}) async {
|
||||||
log(request.toRequest().toString());
|
log(request.toRequest().toString());
|
||||||
try {
|
try {
|
||||||
final response = await _apiClient.post(
|
final response = await _apiClient.post(
|
||||||
"${ApiPath.orders}/split-bill",
|
"${ApiPath.orders}/split-bill",
|
||||||
data: request.toRequest(),
|
data: request.toRequest(),
|
||||||
headers: getAuthorizationHeader(),
|
headers: _buildHeaders(idempotencyKey: idempotencyKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
if (response.data['success'] == false) {
|
||||||
@ -267,12 +280,13 @@ class OrderRemoteDataProvider {
|
|||||||
required String id,
|
required String id,
|
||||||
required String reason,
|
required String reason,
|
||||||
required int refundAmount,
|
required int refundAmount,
|
||||||
|
String? idempotencyKey,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await _apiClient.post(
|
final response = await _apiClient.post(
|
||||||
'${ApiPath.orders}/$id/refund',
|
'${ApiPath.orders}/$id/refund',
|
||||||
data: {'refund_amount': refundAmount, 'reason': reason},
|
data: {'refund_amount': refundAmount, 'reason': reason},
|
||||||
headers: getAuthorizationHeader(),
|
headers: _buildHeaders(idempotencyKey: idempotencyKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.data['success'] == false) {
|
if (response.data['success'] == false) {
|
||||||
|
|||||||
@ -110,6 +110,7 @@ class OrderRepository implements IOrderRepository {
|
|||||||
Future<Either<OrderFailure, Order>> addItemOrder({
|
Future<Either<OrderFailure, Order>> addItemOrder({
|
||||||
required String id,
|
required String id,
|
||||||
required List<AddItemOrderRequest> request,
|
required List<AddItemOrderRequest> request,
|
||||||
|
String? idempotencyKey,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final result = await _dataProvider.addItemOrder(
|
final result = await _dataProvider.addItemOrder(
|
||||||
@ -117,6 +118,7 @@ class OrderRepository implements IOrderRepository {
|
|||||||
request: request
|
request: request
|
||||||
.map((e) => AddItemOrderRequestDto.fromDomain(e))
|
.map((e) => AddItemOrderRequestDto.fromDomain(e))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.hasError) {
|
if (result.hasError) {
|
||||||
@ -134,10 +136,12 @@ class OrderRepository implements IOrderRepository {
|
|||||||
@override
|
@override
|
||||||
Future<Either<OrderFailure, Payment>> createPayment({
|
Future<Either<OrderFailure, Payment>> createPayment({
|
||||||
required PaymentRequest request,
|
required PaymentRequest request,
|
||||||
|
String? idempotencyKey,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final result = await _dataProvider.storePayment(
|
final result = await _dataProvider.storePayment(
|
||||||
PaymentRequestDto.fromDomain(request),
|
PaymentRequestDto.fromDomain(request),
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.hasError) {
|
if (result.hasError) {
|
||||||
@ -158,6 +162,7 @@ class OrderRepository implements IOrderRepository {
|
|||||||
required String reason,
|
required String reason,
|
||||||
String type = "ITEM",
|
String type = "ITEM",
|
||||||
required List<OrderItem> orderItems,
|
required List<OrderItem> orderItems,
|
||||||
|
String? idempotencyKey,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final result = await _dataProvider.voidOrder(
|
final result = await _dataProvider.voidOrder(
|
||||||
@ -165,6 +170,7 @@ class OrderRepository implements IOrderRepository {
|
|||||||
reason: reason,
|
reason: reason,
|
||||||
type: type,
|
type: type,
|
||||||
orderItems: orderItems.map((e) => OrderItemDto.fromDomain(e)).toList(),
|
orderItems: orderItems.map((e) => OrderItemDto.fromDomain(e)).toList(),
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.hasError) {
|
if (result.hasError) {
|
||||||
@ -180,11 +186,13 @@ class OrderRepository implements IOrderRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Either<OrderFailure, Payment>> createSplitBill(
|
Future<Either<OrderFailure, Payment>> createSplitBill(
|
||||||
PaymentSplitBillRequest request,
|
PaymentSplitBillRequest request, {
|
||||||
) async {
|
String? idempotencyKey,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
final result = await _dataProvider.createSplitBill(
|
final result = await _dataProvider.createSplitBill(
|
||||||
PaymentSplitBillRequestDto.fromDomain(request),
|
PaymentSplitBillRequestDto.fromDomain(request),
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.hasError) {
|
if (result.hasError) {
|
||||||
@ -204,12 +212,14 @@ class OrderRepository implements IOrderRepository {
|
|||||||
required String id,
|
required String id,
|
||||||
required String reason,
|
required String reason,
|
||||||
required int refundAmount,
|
required int refundAmount,
|
||||||
|
String? idempotencyKey,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final result = await _dataProvider.refundPayment(
|
final result = await _dataProvider.refundPayment(
|
||||||
id: id,
|
id: id,
|
||||||
reason: reason,
|
reason: reason,
|
||||||
refundAmount: refundAmount,
|
refundAmount: refundAmount,
|
||||||
|
idempotencyKey: idempotencyKey,
|
||||||
);
|
);
|
||||||
if (result.hasError) {
|
if (result.hasError) {
|
||||||
return left(result.error!);
|
return left(result.error!);
|
||||||
|
|||||||
@ -28,14 +28,16 @@ class OrderVoidConfirmDialog extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: AppElevatedButton.outlined(
|
child: AppElevatedButton.outlined(
|
||||||
onPressed: () => context.maybePop(),
|
onPressed: state.isSubmitting ? null : () => context.maybePop(),
|
||||||
label: 'Batal',
|
label: 'Batal',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SpaceWidth(16),
|
const SpaceWidth(16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: AppElevatedButton.filled(
|
child: AppElevatedButton.filled(
|
||||||
onPressed: () {
|
onPressed: state.isSubmitting
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
context.read<VoidFormBloc>().add(
|
context.read<VoidFormBloc>().add(
|
||||||
const VoidFormEvent.submitted(),
|
const VoidFormEvent.submitted(),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1374,7 +1374,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||||
|
|||||||
@ -49,6 +49,7 @@ dependencies:
|
|||||||
table_calendar: ^3.1.2
|
table_calendar: ^3.1.2
|
||||||
synchronized: ^3.4.0
|
synchronized: ^3.4.0
|
||||||
collection: ^1.19.1
|
collection: ^1.19.1
|
||||||
|
uuid: ^4.5.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user