feat: implement idempotency key for critical API endpoints

This commit is contained in:
Efril
2026-06-04 00:48:50 +07:00
parent 2ab20a1150
commit b228538725
21 changed files with 431 additions and 37 deletions
@@ -3,6 +3,7 @@ import 'package:dartz/dartz.dart' hide Order;
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order;
import 'package:uuid/uuid.dart';
import '../../../common/types/order_type.dart';
import '../../../domain/customer/customer.dart';
@@ -123,10 +124,15 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
addItemOrder: (e) async {
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(
state.copyWith(
isAddingItemOrder: true,
failureOrAddItemOrder: none(),
addItemIdempotencyKey: idempotencyKey,
),
);
@@ -145,12 +151,16 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
failureOrAddItemOrder = await _repository.addItemOrder(
id: e.orderId,
request: request,
idempotencyKey: idempotencyKey,
);
emit(
state.copyWith(
isAddingItemOrder: false,
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 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
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -1608,6 +1613,7 @@ abstract class $OrderFormStateCopyWith<$Res> {
bool isCreating,
bool isCreatingWithPayment,
bool isAddingItemOrder,
String? addItemIdempotencyKey,
});
$PaymentMethodCopyWith<$Res>? get paymentMethod;
@@ -1638,6 +1644,7 @@ class _$OrderFormStateCopyWithImpl<$Res, $Val extends OrderFormState>
Object? isCreating = null,
Object? isCreatingWithPayment = null,
Object? isAddingItemOrder = null,
Object? addItemIdempotencyKey = freezed,
}) {
return _then(
_value.copyWith(
@@ -1678,6 +1685,10 @@ class _$OrderFormStateCopyWithImpl<$Res, $Val extends OrderFormState>
? _value.isAddingItemOrder
: isAddingItemOrder // ignore: cast_nullable_to_non_nullable
as bool,
addItemIdempotencyKey: freezed == addItemIdempotencyKey
? _value.addItemIdempotencyKey
: addItemIdempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
)
as $Val,
);
@@ -1731,6 +1742,7 @@ abstract class _$$OrderFormStateImplCopyWith<$Res>
bool isCreating,
bool isCreatingWithPayment,
bool isAddingItemOrder,
String? addItemIdempotencyKey,
});
@override
@@ -1762,6 +1774,7 @@ class __$$OrderFormStateImplCopyWithImpl<$Res>
Object? isCreating = null,
Object? isCreatingWithPayment = null,
Object? isAddingItemOrder = null,
Object? addItemIdempotencyKey = freezed,
}) {
return _then(
_$OrderFormStateImpl(
@@ -1801,6 +1814,10 @@ class __$$OrderFormStateImplCopyWithImpl<$Res>
? _value.isAddingItemOrder
: isAddingItemOrder // ignore: cast_nullable_to_non_nullable
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.isCreatingWithPayment = false,
this.isAddingItemOrder = false,
this.addItemIdempotencyKey,
});
@override
@@ -1845,9 +1863,15 @@ class _$OrderFormStateImpl
@JsonKey()
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
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
@@ -1868,7 +1892,10 @@ class _$OrderFormStateImpl
..add(DiagnosticsProperty('failureOrAddItemOrder', failureOrAddItemOrder))
..add(DiagnosticsProperty('isCreating', isCreating))
..add(DiagnosticsProperty('isCreatingWithPayment', isCreatingWithPayment))
..add(DiagnosticsProperty('isAddingItemOrder', isAddingItemOrder));
..add(DiagnosticsProperty('isAddingItemOrder', isAddingItemOrder))
..add(
DiagnosticsProperty('addItemIdempotencyKey', addItemIdempotencyKey),
);
}
@override
@@ -1897,7 +1924,9 @@ class _$OrderFormStateImpl
(identical(other.isCreatingWithPayment, isCreatingWithPayment) ||
other.isCreatingWithPayment == isCreatingWithPayment) &&
(identical(other.isAddingItemOrder, isAddingItemOrder) ||
other.isAddingItemOrder == isAddingItemOrder));
other.isAddingItemOrder == isAddingItemOrder) &&
(identical(other.addItemIdempotencyKey, addItemIdempotencyKey) ||
other.addItemIdempotencyKey == addItemIdempotencyKey));
}
@override
@@ -1912,6 +1941,7 @@ class _$OrderFormStateImpl
isCreating,
isCreatingWithPayment,
isAddingItemOrder,
addItemIdempotencyKey,
);
/// Create a copy of OrderFormState
@@ -1938,6 +1968,7 @@ abstract class _OrderFormState implements OrderFormState {
final bool isCreating,
final bool isCreatingWithPayment,
final bool isAddingItemOrder,
final String? addItemIdempotencyKey,
}) = _$OrderFormStateImpl;
@override
@@ -1959,6 +1990,12 @@ abstract class _OrderFormState implements OrderFormState {
@override
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
/// with the given fields replaced by the non-null parameter values.
@override
@@ -13,6 +13,10 @@ class OrderFormState with _$OrderFormState {
@Default(false) bool isCreating,
@Default(false) bool isCreatingWithPayment,
@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;
factory OrderFormState.initial() => OrderFormState(
@@ -4,6 +4,7 @@ import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart' hide Order;
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order;
import 'package:uuid/uuid.dart';
import '../../../common/types/split_type.dart';
import '../../../domain/order/order.dart';
@@ -38,7 +39,15 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
submitted: (e) async {
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(
orderId: state.order.id,
@@ -58,20 +67,32 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
.toList(),
);
failureOrPayment = await _repository.createPayment(request: request);
failureOrPayment = await _repository.createPayment(
request: request,
idempotencyKey: idempotencyKey,
);
emit(
state.copyWith(
isSubmitting: false,
failureOrPayment: optionOf(failureOrPayment),
// Clear key on success, keep on failure for retry
idempotencyKey: failureOrPayment.isRight() ? null : idempotencyKey,
),
);
},
submittedSplitBill: (e) async {
Either<OrderFailure, Payment> failureOrPayment;
// Generate new idempotency key untuk split bill
final idempotencyKey = state.splitBillIdempotencyKey ?? const Uuid().v4();
emit(
state.copyWith(isSubmitting: true, failureOrPaymentSplitBill: none()),
state.copyWith(
isSubmitting: true,
failureOrPaymentSplitBill: none(),
splitBillIdempotencyKey: idempotencyKey,
),
);
final request = PaymentSplitBillRequest(
@@ -93,12 +114,18 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
log(request.toString());
failureOrPayment = await _repository.createSplitBill(request);
failureOrPayment = await _repository.createSplitBill(
request,
idempotencyKey: idempotencyKey,
);
emit(
state.copyWith(
isSubmitting: false,
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;
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
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -834,6 +842,8 @@ abstract class $PaymentFormStateCopyWith<$Res> {
Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
PaymentMethod? paymentMethod,
bool isSubmitting,
String? idempotencyKey,
String? splitBillIdempotencyKey,
});
$OrderCopyWith<$Res> get order;
@@ -861,6 +871,8 @@ class _$PaymentFormStateCopyWithImpl<$Res, $Val extends PaymentFormState>
Object? failureOrPaymentSplitBill = null,
Object? paymentMethod = freezed,
Object? isSubmitting = null,
Object? idempotencyKey = freezed,
Object? splitBillIdempotencyKey = freezed,
}) {
return _then(
_value.copyWith(
@@ -888,6 +900,14 @@ class _$PaymentFormStateCopyWithImpl<$Res, $Val extends PaymentFormState>
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
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,
);
@@ -934,6 +954,8 @@ abstract class _$$PaymentFormStateImplCopyWith<$Res>
Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
PaymentMethod? paymentMethod,
bool isSubmitting,
String? idempotencyKey,
String? splitBillIdempotencyKey,
});
@override
@@ -962,6 +984,8 @@ class __$$PaymentFormStateImplCopyWithImpl<$Res>
Object? failureOrPaymentSplitBill = null,
Object? paymentMethod = freezed,
Object? isSubmitting = null,
Object? idempotencyKey = freezed,
Object? splitBillIdempotencyKey = freezed,
}) {
return _then(
_$PaymentFormStateImpl(
@@ -989,6 +1013,14 @@ class __$$PaymentFormStateImplCopyWithImpl<$Res>
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
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,
this.paymentMethod,
this.isSubmitting = false,
this.idempotencyKey,
this.splitBillIdempotencyKey,
}) : _pendingItems = pendingItems;
@override
@@ -1026,9 +1060,19 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
@JsonKey()
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
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
@@ -1051,7 +1095,14 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
(identical(other.paymentMethod, paymentMethod) ||
other.paymentMethod == paymentMethod) &&
(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
@@ -1063,6 +1114,8 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
failureOrPaymentSplitBill,
paymentMethod,
isSubmitting,
idempotencyKey,
splitBillIdempotencyKey,
);
/// Create a copy of PaymentFormState
@@ -1086,6 +1139,8 @@ abstract class _PaymentFormState implements PaymentFormState {
failureOrPaymentSplitBill,
final PaymentMethod? paymentMethod,
final bool isSubmitting,
final String? idempotencyKey,
final String? splitBillIdempotencyKey,
}) = _$PaymentFormStateImpl;
@override
@@ -1101,6 +1156,16 @@ abstract class _PaymentFormState implements PaymentFormState {
@override
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
/// with the given fields replaced by the non-null parameter values.
@override
@@ -9,6 +9,12 @@ class PaymentFormState with _$PaymentFormState {
required Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
PaymentMethod? paymentMethod,
@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;
factory PaymentFormState.initial() => PaymentFormState(
@@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart' hide Order;
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order;
import 'package:uuid/uuid.dart';
import '../../../common/data/refund_data.dart';
import '../../../domain/order/order.dart';
@@ -34,7 +35,15 @@ class RefundFormBloc extends Bloc<RefundFormEvent, RefundFormState> {
submitted: (e) async {
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(
id: state.order.id,
@@ -42,12 +51,15 @@ class RefundFormBloc extends Bloc<RefundFormEvent, RefundFormState> {
? state.reason
: state.refundReason?.value ?? '',
refundAmount: state.order.totalAmount,
idempotencyKey: idempotencyKey,
);
emit(
state.copyWith(
isSubmitting: false,
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;
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
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -712,6 +717,7 @@ abstract class $RefundFormStateCopyWith<$Res> {
RefundReason? refundReason,
Option<Either<OrderFailure, Unit>> failureOrRefund,
bool isSubmitting,
String? idempotencyKey,
});
$OrderCopyWith<$Res> get order;
@@ -737,6 +743,7 @@ class _$RefundFormStateCopyWithImpl<$Res, $Val extends RefundFormState>
Object? refundReason = freezed,
Object? failureOrRefund = null,
Object? isSubmitting = null,
Object? idempotencyKey = freezed,
}) {
return _then(
_value.copyWith(
@@ -760,6 +767,10 @@ class _$RefundFormStateCopyWithImpl<$Res, $Val extends RefundFormState>
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
as bool,
idempotencyKey: freezed == idempotencyKey
? _value.idempotencyKey
: idempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
)
as $Val,
);
@@ -791,6 +802,7 @@ abstract class _$$RefundFormStateImplCopyWith<$Res>
RefundReason? refundReason,
Option<Either<OrderFailure, Unit>> failureOrRefund,
bool isSubmitting,
String? idempotencyKey,
});
@override
@@ -816,6 +828,7 @@ class __$$RefundFormStateImplCopyWithImpl<$Res>
Object? refundReason = freezed,
Object? failureOrRefund = null,
Object? isSubmitting = null,
Object? idempotencyKey = freezed,
}) {
return _then(
_$RefundFormStateImpl(
@@ -839,6 +852,10 @@ class __$$RefundFormStateImplCopyWithImpl<$Res>
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
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,
required this.failureOrRefund,
this.isSubmitting = false,
this.idempotencyKey,
});
@override
@@ -867,9 +885,15 @@ class _$RefundFormStateImpl implements _RefundFormState {
@JsonKey()
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
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
@@ -884,7 +908,9 @@ class _$RefundFormStateImpl implements _RefundFormState {
(identical(other.failureOrRefund, failureOrRefund) ||
other.failureOrRefund == failureOrRefund) &&
(identical(other.isSubmitting, isSubmitting) ||
other.isSubmitting == isSubmitting));
other.isSubmitting == isSubmitting) &&
(identical(other.idempotencyKey, idempotencyKey) ||
other.idempotencyKey == idempotencyKey));
}
@override
@@ -895,6 +921,7 @@ class _$RefundFormStateImpl implements _RefundFormState {
refundReason,
failureOrRefund,
isSubmitting,
idempotencyKey,
);
/// Create a copy of RefundFormState
@@ -916,6 +943,7 @@ abstract class _RefundFormState implements RefundFormState {
final RefundReason? refundReason,
required final Option<Either<OrderFailure, Unit>> failureOrRefund,
final bool isSubmitting,
final String? idempotencyKey,
}) = _$RefundFormStateImpl;
@override
@@ -929,6 +957,12 @@ abstract class _RefundFormState implements RefundFormState {
@override
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
/// with the given fields replaced by the non-null parameter values.
@override
@@ -8,6 +8,10 @@ class RefundFormState with _$RefundFormState {
RefundReason? refundReason,
required Option<Either<OrderFailure, Unit>> failureOrRefund,
@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;
factory RefundFormState.initial() => RefundFormState(
@@ -3,6 +3,7 @@ import 'package:collection/collection.dart';
import 'package:dartz/dartz.dart' hide Order;
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order;
import 'package:uuid/uuid.dart';
import '../../../common/types/void_type.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(
orderId: state.order.id,
reason: state.voidReason ?? '',
orderItems: voidItems,
type: state.voidType.toStringType(),
idempotencyKey: idempotencyKey,
);
emit(
@@ -119,6 +129,8 @@ class VoidFormBloc extends Bloc<VoidFormEvent, VoidFormState> {
isSubmitting: false,
failureOrVoid: optionOf(failureOrVoid),
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;
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
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -1280,6 +1285,7 @@ abstract class $VoidFormStateCopyWith<$Res> {
int totalPriceVoid,
Option<Either<OrderFailure, Unit>> failureOrVoid,
bool isSubmitting,
String? idempotencyKey,
});
$OrderCopyWith<$Res> get order;
@@ -1309,6 +1315,7 @@ class _$VoidFormStateCopyWithImpl<$Res, $Val extends VoidFormState>
Object? totalPriceVoid = null,
Object? failureOrVoid = null,
Object? isSubmitting = null,
Object? idempotencyKey = freezed,
}) {
return _then(
_value.copyWith(
@@ -1348,6 +1355,10 @@ class _$VoidFormStateCopyWithImpl<$Res, $Val extends VoidFormState>
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
as bool,
idempotencyKey: freezed == idempotencyKey
? _value.idempotencyKey
: idempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
)
as $Val,
);
@@ -1383,6 +1394,7 @@ abstract class _$$VoidFormStateImplCopyWith<$Res>
int totalPriceVoid,
Option<Either<OrderFailure, Unit>> failureOrVoid,
bool isSubmitting,
String? idempotencyKey,
});
@override
@@ -1412,6 +1424,7 @@ class __$$VoidFormStateImplCopyWithImpl<$Res>
Object? totalPriceVoid = null,
Object? failureOrVoid = null,
Object? isSubmitting = null,
Object? idempotencyKey = freezed,
}) {
return _then(
_$VoidFormStateImpl(
@@ -1451,6 +1464,10 @@ class __$$VoidFormStateImplCopyWithImpl<$Res>
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
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.failureOrVoid,
this.isSubmitting = false,
this.idempotencyKey,
}) : _pendingItems = pendingItems,
_voidItems = voidItems,
_selectedItemQuantities = selectedItemQuantities;
@@ -1512,9 +1530,15 @@ class _$VoidFormStateImpl implements _VoidFormState {
@JsonKey()
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
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
@@ -1544,7 +1568,9 @@ class _$VoidFormStateImpl implements _VoidFormState {
(identical(other.failureOrVoid, failureOrVoid) ||
other.failureOrVoid == failureOrVoid) &&
(identical(other.isSubmitting, isSubmitting) ||
other.isSubmitting == isSubmitting));
other.isSubmitting == isSubmitting) &&
(identical(other.idempotencyKey, idempotencyKey) ||
other.idempotencyKey == idempotencyKey));
}
@override
@@ -1559,6 +1585,7 @@ class _$VoidFormStateImpl implements _VoidFormState {
totalPriceVoid,
failureOrVoid,
isSubmitting,
idempotencyKey,
);
/// Create a copy of VoidFormState
@@ -1581,6 +1608,7 @@ abstract class _VoidFormState implements VoidFormState {
required final int totalPriceVoid,
required final Option<Either<OrderFailure, Unit>> failureOrVoid,
final bool isSubmitting,
final String? idempotencyKey,
}) = _$VoidFormStateImpl;
@override
@@ -1602,6 +1630,12 @@ abstract class _VoidFormState implements VoidFormState {
@override
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
/// with the given fields replaced by the non-null parameter values.
@override
@@ -12,6 +12,10 @@ class VoidFormState with _$VoidFormState {
required int totalPriceVoid,
required Option<Either<OrderFailure, Unit>> failureOrVoid,
@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;
factory VoidFormState.initial() => VoidFormState(