feat: create payment

This commit is contained in:
efrilm 2025-08-03 20:46:57 +07:00
parent 903e7d8c80
commit 5d74ed0bed
12 changed files with 1678 additions and 7 deletions

View File

@ -6,8 +6,10 @@ import 'package:dio/dio.dart';
import 'package:enaklo_pos/core/constants/variables.dart';
import 'package:enaklo_pos/core/network/dio_client.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/data/models/request/payment_request.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:enaklo_pos/data/models/response/payment_method_response_model.dart';
import 'package:enaklo_pos/data/models/response/payment_response_model.dart';
import 'package:enaklo_pos/data/models/response/summary_response_model.dart';
import 'package:enaklo_pos/presentation/home/models/order_model.dart';
import 'package:enaklo_pos/presentation/home/models/order_request.dart';
@ -225,6 +227,40 @@ class OrderRemoteDatasource {
}
}
Future<Either<String, PaymentSuccessResponseModel>> createPayment(
PaymentRequestModel orderModel) async {
final authData = await AuthLocalDataSource().getAuthData();
final url = '${Variables.baseUrl}/api/v1/payments';
try {
final response = await dio.post(
url,
data: orderModel.toMap(),
options: Options(
headers: {
'Authorization': 'Bearer ${authData.token}',
'Accept': 'application/json',
'Content-Type': 'application/json',
},
),
);
if (response.statusCode == 200) {
return Right(PaymentSuccessResponseModel.fromMap(response.data));
} else {
return const Left('Gagal membuat pembayaran');
}
} on DioException catch (e) {
final errorMessage = e.response?.data['message'] ?? 'Kesalahan jaringan';
log("💥 Dio error: ${e.message}");
log("💥 Dio response: ${e.response?.data}");
return Left(errorMessage);
} catch (e) {
log("💥 Unexpected error: $e");
return const Left('Terjadi kesalahan tak terduga');
}
}
Future<Either<String, OrderResponseModel>> getOrder(
{int page = 1,
int limit = Variables.defaultLimit,
@ -248,11 +284,7 @@ class OrderRemoteDatasource {
),
);
log("📥 HTTP Status Code: ${response.statusCode}");
log("📥 Response Body: ${response.data}");
if (response.statusCode == 200) {
log("✅ getOrderByRangeDate API call successful");
return Right(OrderResponseModel.fromMap(response.data));
} else {
log("❌ getOrderByRangeDate API call failed - Status: ${response.statusCode}");

View File

@ -0,0 +1,80 @@
import 'dart:convert';
class PaymentRequestModel {
final String? orderId;
final String? paymentMethodId;
final int? amount;
final String? transactionId;
final int? splitNumber;
final int? splitTotal;
final String? splitDescription;
final List<PaymentOrderItemModel>? paymentOrderItems;
PaymentRequestModel({
this.orderId,
this.paymentMethodId,
this.amount,
this.transactionId,
this.splitNumber,
this.splitTotal,
this.splitDescription,
this.paymentOrderItems,
});
factory PaymentRequestModel.fromJson(String str) =>
PaymentRequestModel.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory PaymentRequestModel.fromMap(Map<String, dynamic> json) =>
PaymentRequestModel(
orderId: json["order_id"],
paymentMethodId: json["payment_method_id"],
amount: json["amount"]?.toDouble(),
transactionId: json["transaction_id"],
splitNumber: json["split_number"],
splitTotal: json["split_total"],
splitDescription: json["split_description"],
paymentOrderItems: json["payment_order_items"] == null
? []
: List<PaymentOrderItemModel>.from(json["payment_order_items"]
.map((x) => PaymentOrderItemModel.fromMap(x))),
);
Map<String, dynamic> toMap() => {
"order_id": orderId,
"payment_method_id": paymentMethodId,
"amount": amount,
"transaction_id": transactionId,
"split_number": splitNumber,
"split_total": splitTotal,
"split_description": splitDescription,
"payment_order_items": paymentOrderItems == null
? []
: List<dynamic>.from(paymentOrderItems!.map((x) => x.toMap())),
};
}
class PaymentOrderItemModel {
final String? orderItemId;
final int? amount;
PaymentOrderItemModel({
this.orderItemId,
this.amount,
});
factory PaymentOrderItemModel.fromJson(String str) =>
PaymentOrderItemModel.fromMap(json.decode(str));
factory PaymentOrderItemModel.fromMap(Map<String, dynamic> json) =>
PaymentOrderItemModel(
orderItemId: json["order_item_id"],
amount: json["amount"]?.toDouble(),
);
Map<String, dynamic> toMap() => {
"order_item_id": orderItemId,
"amount": amount,
};
}

View File

@ -0,0 +1,95 @@
import 'dart:convert';
class PaymentSuccessResponseModel {
final bool? success;
final PaymentData? data;
final dynamic errors;
PaymentSuccessResponseModel({
this.success,
this.data,
this.errors,
});
factory PaymentSuccessResponseModel.fromJson(String str) =>
PaymentSuccessResponseModel.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory PaymentSuccessResponseModel.fromMap(Map<String, dynamic> json) =>
PaymentSuccessResponseModel(
success: json["success"],
data: json["data"] == null ? null : PaymentData.fromMap(json["data"]),
errors: json["errors"],
);
Map<String, dynamic> toMap() => {
"success": success,
"data": data?.toMap(),
"errors": errors,
};
}
class PaymentData {
final String? id;
final String? orderId;
final String? paymentMethodId;
final int? amount;
final String? status;
final String? transactionId;
final int? splitNumber;
final int? splitTotal;
final String? splitDescription;
final int? refundAmount;
final DateTime? createdAt;
final DateTime? updatedAt;
PaymentData({
this.id,
this.orderId,
this.paymentMethodId,
this.amount,
this.status,
this.transactionId,
this.splitNumber,
this.splitTotal,
this.splitDescription,
this.refundAmount,
this.createdAt,
this.updatedAt,
});
factory PaymentData.fromMap(Map<String, dynamic> json) => PaymentData(
id: json["id"],
orderId: json["order_id"],
paymentMethodId: json["payment_method_id"],
amount: json["amount"],
status: json["status"],
transactionId: json["transaction_id"],
splitNumber: json["split_number"],
splitTotal: json["split_total"],
splitDescription: json["split_description"],
refundAmount: json["refund_amount"],
createdAt: json["created_at"] == null
? null
: DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null
? null
: DateTime.parse(json["updated_at"]),
);
Map<String, dynamic> toMap() => {
"id": id,
"order_id": orderId,
"payment_method_id": paymentMethodId,
"amount": amount,
"status": status,
"transaction_id": transactionId,
"split_number": splitNumber,
"split_total": splitTotal,
"split_description": splitDescription,
"refund_amount": refundAmount,
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
};
}

View File

@ -8,6 +8,7 @@ import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dar
import 'package:enaklo_pos/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart';
import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart';
import 'package:enaklo_pos/presentation/sales/blocs/payment_form/payment_form_bloc.dart';
import 'package:flutter/material.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/data/datasources/auth_remote_datasource.dart';
@ -243,6 +244,9 @@ class _MyAppState extends State<MyApp> {
BlocProvider(
create: (context) => CustomerFormBloc(CustomerRemoteDataSource()),
),
BlocProvider(
create: (context) => PaymentFormBloc(OrderRemoteDatasource()),
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,

View File

@ -13,8 +13,8 @@ class OrderLoaderBloc extends Bloc<OrderLoaderEvent, OrderLoaderState> {
: super(OrderLoaderState.initial()) {
on<_GetByStatus>((event, emit) async {
emit(const _Loading());
final result =
await _orderRemoteDatasource.getOrder(status: event.status);
final result = await _orderRemoteDatasource.getOrder(
status: event.status, limit: 20);
result.fold(
(l) => emit(_Error(l)),
(r) => emit(_Loaded(

View File

@ -0,0 +1,33 @@
import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart';
import 'package:enaklo_pos/data/models/request/payment_request.dart';
import 'package:enaklo_pos/data/models/response/payment_response_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'payment_form_event.dart';
part 'payment_form_state.dart';
part 'payment_form_bloc.freezed.dart';
class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
final OrderRemoteDatasource _orderRemoteDatasource;
PaymentFormBloc(this._orderRemoteDatasource)
: super(PaymentFormState.initial()) {
on<_Create>(
(event, emit) async {
emit(const _Loading());
try {
final result =
await _orderRemoteDatasource.createPayment(event.payment);
result.fold(
(error) => emit(_Error(error)),
(success) => emit(_Success(success.data!)),
);
} catch (e) {
emit(_Error("Failed to create payment: $e"));
}
},
);
}
}

View File

@ -0,0 +1,845 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'payment_form_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$PaymentFormEvent {
PaymentRequestModel get payment => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(PaymentRequestModel payment) create,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(PaymentRequestModel payment)? create,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(PaymentRequestModel payment)? create,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Create value) create,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Create value)? create,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Create value)? create,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
/// Create a copy of PaymentFormEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PaymentFormEventCopyWith<PaymentFormEvent> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PaymentFormEventCopyWith<$Res> {
factory $PaymentFormEventCopyWith(
PaymentFormEvent value, $Res Function(PaymentFormEvent) then) =
_$PaymentFormEventCopyWithImpl<$Res, PaymentFormEvent>;
@useResult
$Res call({PaymentRequestModel payment});
}
/// @nodoc
class _$PaymentFormEventCopyWithImpl<$Res, $Val extends PaymentFormEvent>
implements $PaymentFormEventCopyWith<$Res> {
_$PaymentFormEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PaymentFormEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? payment = null,
}) {
return _then(_value.copyWith(
payment: null == payment
? _value.payment
: payment // ignore: cast_nullable_to_non_nullable
as PaymentRequestModel,
) as $Val);
}
}
/// @nodoc
abstract class _$$CreateImplCopyWith<$Res>
implements $PaymentFormEventCopyWith<$Res> {
factory _$$CreateImplCopyWith(
_$CreateImpl value, $Res Function(_$CreateImpl) then) =
__$$CreateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({PaymentRequestModel payment});
}
/// @nodoc
class __$$CreateImplCopyWithImpl<$Res>
extends _$PaymentFormEventCopyWithImpl<$Res, _$CreateImpl>
implements _$$CreateImplCopyWith<$Res> {
__$$CreateImplCopyWithImpl(
_$CreateImpl _value, $Res Function(_$CreateImpl) _then)
: super(_value, _then);
/// Create a copy of PaymentFormEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? payment = null,
}) {
return _then(_$CreateImpl(
null == payment
? _value.payment
: payment // ignore: cast_nullable_to_non_nullable
as PaymentRequestModel,
));
}
}
/// @nodoc
class _$CreateImpl implements _Create {
const _$CreateImpl(this.payment);
@override
final PaymentRequestModel payment;
@override
String toString() {
return 'PaymentFormEvent.create(payment: $payment)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$CreateImpl &&
(identical(other.payment, payment) || other.payment == payment));
}
@override
int get hashCode => Object.hash(runtimeType, payment);
/// Create a copy of PaymentFormEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$CreateImplCopyWith<_$CreateImpl> get copyWith =>
__$$CreateImplCopyWithImpl<_$CreateImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(PaymentRequestModel payment) create,
}) {
return create(payment);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(PaymentRequestModel payment)? create,
}) {
return create?.call(payment);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(PaymentRequestModel payment)? create,
required TResult orElse(),
}) {
if (create != null) {
return create(payment);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Create value) create,
}) {
return create(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Create value)? create,
}) {
return create?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Create value)? create,
required TResult orElse(),
}) {
if (create != null) {
return create(this);
}
return orElse();
}
}
abstract class _Create implements PaymentFormEvent {
const factory _Create(final PaymentRequestModel payment) = _$CreateImpl;
@override
PaymentRequestModel get payment;
/// Create a copy of PaymentFormEvent
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$CreateImplCopyWith<_$CreateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$PaymentFormState {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PaymentData data) success,
required TResult Function(String message) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PaymentData data)? success,
TResult? Function(String message)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PaymentData data)? success,
TResult Function(String message)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
required TResult Function(_Error value) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
TResult? Function(_Error value)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
TResult Function(_Error value)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PaymentFormStateCopyWith<$Res> {
factory $PaymentFormStateCopyWith(
PaymentFormState value, $Res Function(PaymentFormState) then) =
_$PaymentFormStateCopyWithImpl<$Res, PaymentFormState>;
}
/// @nodoc
class _$PaymentFormStateCopyWithImpl<$Res, $Val extends PaymentFormState>
implements $PaymentFormStateCopyWith<$Res> {
_$PaymentFormStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PaymentFormState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$InitialImplCopyWith<$Res> {
factory _$$InitialImplCopyWith(
_$InitialImpl value, $Res Function(_$InitialImpl) then) =
__$$InitialImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$InitialImplCopyWithImpl<$Res>
extends _$PaymentFormStateCopyWithImpl<$Res, _$InitialImpl>
implements _$$InitialImplCopyWith<$Res> {
__$$InitialImplCopyWithImpl(
_$InitialImpl _value, $Res Function(_$InitialImpl) _then)
: super(_value, _then);
/// Create a copy of PaymentFormState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$InitialImpl implements _Initial {
const _$InitialImpl();
@override
String toString() {
return 'PaymentFormState.initial()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$InitialImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PaymentData data) success,
required TResult Function(String message) error,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PaymentData data)? success,
TResult? Function(String message)? error,
}) {
return initial?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PaymentData data)? success,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
required TResult Function(_Error value) error,
}) {
return initial(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
TResult? Function(_Error value)? error,
}) {
return initial?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class _Initial implements PaymentFormState {
const factory _Initial() = _$InitialImpl;
}
/// @nodoc
abstract class _$$LoadingImplCopyWith<$Res> {
factory _$$LoadingImplCopyWith(
_$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
__$$LoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LoadingImplCopyWithImpl<$Res>
extends _$PaymentFormStateCopyWithImpl<$Res, _$LoadingImpl>
implements _$$LoadingImplCopyWith<$Res> {
__$$LoadingImplCopyWithImpl(
_$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
: super(_value, _then);
/// Create a copy of PaymentFormState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LoadingImpl implements _Loading {
const _$LoadingImpl();
@override
String toString() {
return 'PaymentFormState.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LoadingImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PaymentData data) success,
required TResult Function(String message) error,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PaymentData data)? success,
TResult? Function(String message)? error,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PaymentData data)? success,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
required TResult Function(_Error value) error,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
TResult? Function(_Error value)? error,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class _Loading implements PaymentFormState {
const factory _Loading() = _$LoadingImpl;
}
/// @nodoc
abstract class _$$SuccessImplCopyWith<$Res> {
factory _$$SuccessImplCopyWith(
_$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
__$$SuccessImplCopyWithImpl<$Res>;
@useResult
$Res call({PaymentData data});
}
/// @nodoc
class __$$SuccessImplCopyWithImpl<$Res>
extends _$PaymentFormStateCopyWithImpl<$Res, _$SuccessImpl>
implements _$$SuccessImplCopyWith<$Res> {
__$$SuccessImplCopyWithImpl(
_$SuccessImpl _value, $Res Function(_$SuccessImpl) _then)
: super(_value, _then);
/// Create a copy of PaymentFormState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? data = freezed,
}) {
return _then(_$SuccessImpl(
freezed == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as PaymentData,
));
}
}
/// @nodoc
class _$SuccessImpl implements _Success {
const _$SuccessImpl(this.data);
@override
final PaymentData data;
@override
String toString() {
return 'PaymentFormState.success(data: $data)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SuccessImpl &&
const DeepCollectionEquality().equals(other.data, data));
}
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(data));
/// Create a copy of PaymentFormState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SuccessImplCopyWith<_$SuccessImpl> get copyWith =>
__$$SuccessImplCopyWithImpl<_$SuccessImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PaymentData data) success,
required TResult Function(String message) error,
}) {
return success(data);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PaymentData data)? success,
TResult? Function(String message)? error,
}) {
return success?.call(data);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PaymentData data)? success,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (success != null) {
return success(data);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
required TResult Function(_Error value) error,
}) {
return success(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
TResult? Function(_Error value)? error,
}) {
return success?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (success != null) {
return success(this);
}
return orElse();
}
}
abstract class _Success implements PaymentFormState {
const factory _Success(final PaymentData data) = _$SuccessImpl;
PaymentData get data;
/// Create a copy of PaymentFormState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SuccessImplCopyWith<_$SuccessImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$ErrorImplCopyWith<$Res> {
factory _$$ErrorImplCopyWith(
_$ErrorImpl value, $Res Function(_$ErrorImpl) then) =
__$$ErrorImplCopyWithImpl<$Res>;
@useResult
$Res call({String message});
}
/// @nodoc
class __$$ErrorImplCopyWithImpl<$Res>
extends _$PaymentFormStateCopyWithImpl<$Res, _$ErrorImpl>
implements _$$ErrorImplCopyWith<$Res> {
__$$ErrorImplCopyWithImpl(
_$ErrorImpl _value, $Res Function(_$ErrorImpl) _then)
: super(_value, _then);
/// Create a copy of PaymentFormState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? message = null,
}) {
return _then(_$ErrorImpl(
null == message
? _value.message
: message // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$ErrorImpl implements _Error {
const _$ErrorImpl(this.message);
@override
final String message;
@override
String toString() {
return 'PaymentFormState.error(message: $message)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ErrorImpl &&
(identical(other.message, message) || other.message == message));
}
@override
int get hashCode => Object.hash(runtimeType, message);
/// Create a copy of PaymentFormState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
__$$ErrorImplCopyWithImpl<_$ErrorImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PaymentData data) success,
required TResult Function(String message) error,
}) {
return error(message);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PaymentData data)? success,
TResult? Function(String message)? error,
}) {
return error?.call(message);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PaymentData data)? success,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(message);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
required TResult Function(_Error value) error,
}) {
return error(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
TResult? Function(_Error value)? error,
}) {
return error?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this);
}
return orElse();
}
}
abstract class _Error implements PaymentFormState {
const factory _Error(final String message) = _$ErrorImpl;
String get message;
/// Create a copy of PaymentFormState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,6 @@
part of 'payment_form_bloc.dart';
@freezed
class PaymentFormEvent with _$PaymentFormEvent {
const factory PaymentFormEvent.create(PaymentRequestModel payment) = _Create;
}

View File

@ -0,0 +1,9 @@
part of 'payment_form_bloc.dart';
@freezed
class PaymentFormState with _$PaymentFormState {
const factory PaymentFormState.initial() = _Initial;
const factory PaymentFormState.loading() = _Loading;
const factory PaymentFormState.success(PaymentData data) = _Success;
const factory PaymentFormState.error(String message) = _Error;
}

View File

@ -0,0 +1,397 @@
import 'dart:developer';
import 'package:enaklo_pos/core/components/buttons.dart';
import 'package:enaklo_pos/core/components/custom_modal_dialog.dart';
import 'package:enaklo_pos/core/components/flushbar.dart';
import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/models/request/payment_request.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:enaklo_pos/data/models/response/payment_methods_response_model.dart';
import 'package:enaklo_pos/presentation/home/bloc/payment_methods/payment_methods_bloc.dart';
import 'package:enaklo_pos/presentation/sales/blocs/payment_form/payment_form_bloc.dart';
import 'package:enaklo_pos/presentation/success/pages/success_payment_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class PaymentDialog extends StatefulWidget {
final Order order;
const PaymentDialog({super.key, required this.order});
@override
State<PaymentDialog> createState() => _PaymentDialogState();
}
class _PaymentDialogState extends State<PaymentDialog> {
PaymentMethod? selectedPaymentMethod;
final totalPriceController = TextEditingController();
int priceValue = 0;
int uangPas = 0;
int uangPas2 = 0;
int uangPas3 = 0;
init() {
setState(() {
uangPas = widget.order.totalAmount ?? 0;
uangPas2 = 50000;
uangPas3 = 100000;
});
}
@override
void initState() {
super.initState();
context
.read<PaymentMethodsBloc>()
.add(PaymentMethodsEvent.fetchPaymentMethods());
init();
}
@override
void dispose() {
super.dispose();
totalPriceController.dispose();
}
@override
Widget build(BuildContext context) {
return CustomModalDialog(
title: 'Pembayaran',
subtitle: 'Silahkan lakukan pembayaran',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: AppColors.grey,
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Metode Pembayaran',
style: TextStyle(
color: AppColors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SpaceHeight(12.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('No. Pesanan'),
Text(
widget.order.orderNumber ?? "",
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
const SpaceHeight(6),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Tagihan'),
Text(
(widget.order.totalAmount ?? 0).currencyFormatRpV2,
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
],
),
),
Container(
padding: const EdgeInsets.all(16),
width: double.infinity,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: AppColors.grey,
width: 1.0,
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Metode Pembayaran',
style: TextStyle(
color: AppColors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SpaceHeight(12.0),
BlocBuilder<PaymentMethodsBloc, PaymentMethodsState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
loading: () => const Center(
child: Column(
children: [
CircularProgressIndicator(),
SizedBox(height: 8.0),
Text('Loading payment methods...'),
],
),
),
error: (message) => Column(
children: [
Center(
child:
Text('Error loading payment methods: $message'),
),
const SpaceHeight(16.0),
Button.filled(
onPressed: () {
context.read<PaymentMethodsBloc>().add(
PaymentMethodsEvent.fetchPaymentMethods());
},
label: 'Retry',
),
],
),
loaded: (paymentMethods) {
log("Loaded ${paymentMethods.length} payment methods");
paymentMethods.forEach((method) {
log("Payment method: ${method.name} (ID: ${method.id})");
});
if (paymentMethods.isEmpty) {
return Column(
children: [
const Center(
child: Text('No payment methods available'),
),
const SpaceHeight(16.0),
Button.filled(
onPressed: () {
context.read<PaymentMethodsBloc>().add(
PaymentMethodsEvent
.fetchPaymentMethods());
},
label: 'Retry',
),
],
);
}
// Set default selected payment method if none selected or if current selection is not in the list
if (selectedPaymentMethod == null ||
!paymentMethods.any((method) =>
method.id == selectedPaymentMethod?.id)) {
selectedPaymentMethod = paymentMethods.first;
}
return Wrap(
spacing: 12.0,
runSpacing: 8.0,
children: paymentMethods.map((method) {
final isSelected =
selectedPaymentMethod?.id == method.id;
return GestureDetector(
onTap: () {
setState(() {
selectedPaymentMethod = method;
});
},
child: Container(
height: 60,
width: 80,
alignment: Alignment.center,
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: isSelected
? AppColors.primary
: AppColors.white,
border: Border.all(
color: AppColors.primary,
width: 1.0,
),
borderRadius: BorderRadius.circular(8.0),
),
child: Text(
method.name ?? "",
style: TextStyle(
color: isSelected
? AppColors.white
: AppColors.primary,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
);
}).toList(),
);
},
);
},
),
],
),
),
if (selectedPaymentMethod?.type == "cash")
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: AppColors.grey,
width: 1.0,
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Total Bayar',
style: TextStyle(
color: AppColors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SpaceHeight(8.0),
TextFormField(
controller: totalPriceController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
),
hintText: 'Total harga',
),
onChanged: (value) {
priceValue = value.toIntegerFromText;
final int newValue = value.toIntegerFromText;
totalPriceController.text = newValue.currencyFormatRp;
totalPriceController.selection =
TextSelection.fromPosition(TextPosition(
offset: totalPriceController.text.length));
},
),
const SpaceHeight(20.0),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
Button.outlined(
width: 150.0,
onPressed: () {
totalPriceController.text =
uangPas.toString().currencyFormatRpV2;
priceValue = uangPas;
},
label: 'UANG PAS',
),
const SpaceWidth(20.0),
Button.outlined(
width: 150.0,
onPressed: () {
totalPriceController.text =
uangPas2.toString().currencyFormatRpV2;
priceValue = uangPas2;
},
label: uangPas2.toString().currencyFormatRpV2,
),
const SpaceWidth(20.0),
Button.outlined(
width: 150.0,
onPressed: () {
totalPriceController.text =
uangPas3.toString().currencyFormatRpV2;
priceValue = uangPas3;
},
label: uangPas3.toString().currencyFormatRpV2,
),
],
),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 24,
horizontal: 16,
),
child: BlocListener<PaymentFormBloc, PaymentFormState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
success: (data) {
context.pushReplacement(SuccessPaymentPage(
payment: data,
));
},
error: (message) {
AppFlushbar.showError(context, message);
},
);
},
child: BlocBuilder<PaymentFormBloc, PaymentFormState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => Button.filled(
onPressed: () {
if (selectedPaymentMethod == null) {
AppFlushbar.showError(context,
'Pilih metode pembayaran terlebih dahulu');
return;
}
if (selectedPaymentMethod?.type == "cash") {
if (priceValue == 0) {
AppFlushbar.showError(
context, 'Total bayar tidak boleh 0');
return;
}
}
final request = PaymentRequestModel(
amount: widget.order.totalAmount ?? 0,
orderId: widget.order.id,
paymentMethodId: selectedPaymentMethod?.id,
splitDescription: '',
splitNumber: 1,
splitTotal: 1,
transactionId: '',
paymentOrderItems: widget.order.orderItems
?.map((item) => PaymentOrderItemModel(
orderItemId: item.id,
amount: item.totalPrice,
))
.toList());
context
.read<PaymentFormBloc>()
.add(PaymentFormEvent.create(request));
},
label: 'Bayar',
),
loading: () => Center(
child: const CircularProgressIndicator(),
),
);
},
),
),
),
],
),
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:enaklo_pos/presentation/sales/blocs/day_sales/day_sales_bloc.dart';
import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart';
import 'package:enaklo_pos/presentation/sales/dialog/payment_dialog.dart';
import 'package:enaklo_pos/presentation/sales/widgets/sales_detail.dart';
import 'package:enaklo_pos/presentation/sales/widgets/sales_list_order.dart';
import 'package:enaklo_pos/presentation/sales/widgets/sales_order_information.dart';
@ -171,7 +172,12 @@ class _SalesPageState extends State<SalesPage> {
),
SpaceWidth(8),
Button.outlined(
onPressed: () {},
onPressed: () => showDialog(
context: context,
builder: (context) => PaymentDialog(
order: orderDetail!,
),
),
label: 'Bayar',
icon: Icon(Icons.payment),
),

View File

@ -0,0 +1,164 @@
import 'package:enaklo_pos/core/components/buttons.dart';
import 'package:enaklo_pos/core/components/dashed_divider.dart';
import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/models/response/payment_response_model.dart';
import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart';
import 'package:flutter/material.dart';
class SuccessPaymentPage extends StatelessWidget {
final PaymentData payment;
const SuccessPaymentPage({super.key, required this.payment});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
body: Center(
child: Container(
width: context.deviceWidth * 0.4,
height: context.deviceHeight * 0.8,
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
'Pembayaran!',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
),
Text('Pembayaran berhasil dilalukan',
style: const TextStyle(fontSize: 14)),
],
),
),
DashedDivider(
color: AppColors.grey,
),
SpaceHeight(24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Icon(
Icons.check_circle_outline,
size: 64,
color: Colors.green,
),
),
Spacer(),
Padding(
padding: const EdgeInsets.all(16.0).copyWith(top: 24),
child: Column(
children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// 'No. Pesanan',
// ),
// Text(
// order.orderNumber ?? "-",
// style: const TextStyle(fontWeight: FontWeight.bold),
// ),
// ],
// ),
SpaceHeight(4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Waktu',
),
Text(
(payment.createdAt ?? DateTime.now())
.toFormattedDate3(),
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
],
),
),
DashedDivider(
color: AppColors.grey,
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Status Pembayaran',
),
Text(
'Lunas',
style: const TextStyle(
fontWeight: FontWeight.bold, color: Colors.green),
),
],
),
),
DashedDivider(
color: AppColors.grey,
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total Pembayaran',
),
Text(
(payment.amount ?? 0).toString().currencyFormatRpV2,
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
),
DashedDivider(
color: AppColors.grey,
),
Spacer(),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Expanded(
child: Button.outlined(
onPressed: () =>
context.pushReplacement(DashboardPage()),
label: 'Kembali',
height: 44,
),
),
SpaceWidth(12),
Expanded(
child: Button.filled(
onPressed: () {},
label: 'Cetak',
icon: Icon(
Icons.print,
color: AppColors.white,
),
height: 44,
),
),
],
),
),
],
),
),
),
);
}
}