From 644df8211db020c3e28aee07a7dc5c90c64b1a90 Mon Sep 17 00:00:00 2001 From: MooN <56061215+MgKyawLay@users.noreply.github.com> Date: Wed, 12 Nov 2025 21:16:22 +0630 Subject: [PATCH] qr_refundable_from_list --- .idea/deviceManager.xml | 13 + .../kbz/ui/core_ui/InputPasswordFragment.java | 8 + .../utsmm/kbz/ui/qr_pay/QRPayFragment.java | 6 + .../utsmm/kbz/ui/qr_pay/QRPayViewModel.java | 12 + .../kbz/ui/qr_pay/QRRefundDetailFragment.java | 357 ++++++++++++++++++ .../utsmm/kbz/ui/qr_pay/QRRefundFragment.java | 38 +- .../kbz/ui/qr_pay/QRRefundViewAdapter.java | 29 +- .../res/layout/fragment_qr_refund_detail.xml | 349 +++++++++++++++++ app/src/main/res/layout/item_qr_refund.xml | 4 +- .../main/res/navigation/mobile_navigation.xml | 37 ++ .../baselib/db/dao/PayDetailDao.java | 3 +- 11 files changed, 831 insertions(+), 25 deletions(-) create mode 100644 .idea/deviceManager.xml create mode 100644 app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRRefundDetailFragment.java create mode 100644 app/src/main/res/layout/fragment_qr_refund_detail.xml diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/utsmm/kbz/ui/core_ui/InputPasswordFragment.java b/app/src/main/java/com/utsmm/kbz/ui/core_ui/InputPasswordFragment.java index 9e4a163..2644abf 100644 --- a/app/src/main/java/com/utsmm/kbz/ui/core_ui/InputPasswordFragment.java +++ b/app/src/main/java/com/utsmm/kbz/ui/core_ui/InputPasswordFragment.java @@ -142,6 +142,9 @@ public class InputPasswordFragment extends DataBindingFragment implements DataBi private void checkRoute() { switch (Objects.requireNonNull(sharedViewModel.transactionsType.getValue())) { + case MMQR_REFUND: + inputPasswordViewModel.passwordType.setValue(InputPasswordType.SYSTEM); + break; case PRE_AUTH_COMPLETE_VOID: inputPasswordViewModel.passwordType.setValue(InputPasswordType.SYSTEM); sharedViewModel.transactionName.postValue(getResourceString(R.string.title_pre_auth_complete)); @@ -242,6 +245,11 @@ public class InputPasswordFragment extends DataBindingFragment implements DataBi if(inputPasswordViewModel.onClickEnter()) { clearLiveData(); + if(sharedViewModel.transactionsType.getValue() == TransactionsType.MMQR_REFUND){ + safePopBackStack(); + return; + } + if(sharedViewModel.transactionsType.getValue() == TransactionsType.SETTING) { mListener.onSuccess(); } diff --git a/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRPayFragment.java b/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRPayFragment.java index 4a0e815..fdbc787 100644 --- a/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRPayFragment.java +++ b/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRPayFragment.java @@ -80,6 +80,12 @@ public class QRPayFragment extends DataBindingFragment { return config; } + public class ClickEvent { + public void onGenerateQRClick() { onClickQRPay(); } + public void onRefundClick() { onClickRefund(); } + public void onHistoryClick() { onClickHistory(); } + } + @Override protected int currentId() { return currentId; diff --git a/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRPayViewModel.java b/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRPayViewModel.java index adab230..d2f3df0 100644 --- a/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRPayViewModel.java +++ b/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRPayViewModel.java @@ -1,6 +1,7 @@ package com.utsmm.kbz.ui.qr_pay; import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import com.utsmyanmar.baselib.repo.Repository; @@ -18,6 +19,10 @@ public class QRPayViewModel extends ViewModel { private final Repository repository; private final LiveData> refundableHistory; + private final MutableLiveData _payDetail = new MutableLiveData<>(); + public LiveData payDetail = _payDetail; + + @Inject public QRPayViewModel(Repository repository){ this.repository = repository; @@ -27,4 +32,11 @@ public class QRPayViewModel extends ViewModel { public LiveData> getRefundableQrHistory(){ return refundableHistory; } + + public void setPayDetail(PayDetail payDetail){ + _payDetail.setValue(payDetail); + } + + public Runnable onCancel; + public Runnable onConfirm; } \ No newline at end of file diff --git a/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRRefundDetailFragment.java b/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRRefundDetailFragment.java new file mode 100644 index 0000000..fb0c98d --- /dev/null +++ b/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRRefundDetailFragment.java @@ -0,0 +1,357 @@ +package com.utsmm.kbz.ui.qr_pay; + +import android.os.Bundle; +import android.text.InputFilter; +import android.view.View; +import android.widget.EditText; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.utsmm.kbz.ui.kpay.KPayViewModel; +import com.utsmyanmar.baselib.fragment.DataBindingFragment; +import com.utsmyanmar.baselib.network.model.KPayRefund; +import com.utsmyanmar.baselib.util.DataBindingConfig; +import com.utsmyanmar.paylibs.model.PayDetail; +import com.utsmyanmar.paylibs.model.TradeData; +import com.utsmyanmar.paylibs.system.SystemDateTime; +import com.utsmyanmar.paylibs.utils.POSUtil; +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation; +import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType; +import com.utsmm.kbz.BR; +import com.utsmm.kbz.R; +import com.utsmm.kbz.config.Constants; +import com.utsmm.kbz.ui.core_viewmodel.SharedViewModel; +import com.utsmm.kbz.util.DecimalDigitsInputFilter; +import com.utsmm.kbz.util.TransactionUtil; +import com.utsmm.kbz.util.ecr.CoreUtils; + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.disposables.CompositeDisposable; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.schedulers.Schedulers; +import com.utsmyanmar.paylibs.utils.LogUtil; + +public class QRRefundDetailFragment extends DataBindingFragment { + + private SharedViewModel sharedViewModel; + private KPayViewModel KPayViewModel; + private int routeId; + + + private TradeData tradeData; + private PayDetail payDetail; + + // UI Elements + private RadioGroup radioGroupRefundType; + private RadioButton radioOriginal, radioPartial; + private EditText etReferenceNo, etOriginalAmount, etRefundAmount, etRefundReason; + private LinearLayout originalAmountLayout, refundAmountLayout; + + private boolean isPartialRefund = false; + + CompositeDisposable refundDisposable = new CompositeDisposable(); + CompositeDisposable retrieveUpdateDisposable = new CompositeDisposable(); + + private static final String TAG = com.utsmm.kbz.ui.kpay.QRRefundFragment.class.getSimpleName(); + + private static final int hostId = Constants.NAV_HOST_ID; + private static final int currentId = R.id.qrRefundDetail; + + @Override + protected void initViewModel() { + sharedViewModel = getFragmentScopeViewModel(SharedViewModel.class); + KPayViewModel = getFragmentScopeViewModel(KPayViewModel.class); + } + + @Override + protected DataBindingConfig getDataBindingConfig() { + return new DataBindingConfig(R.layout.fragment_qr_refund_detail, BR.sharedViewModel, sharedViewModel) + .addBindingParam(BR.kPayViewModel, KPayViewModel) + .addBindingParam(BR.click, new ClickEvent()); + } + + @Override + protected int currentId() { + return currentId; + } + + @Override + protected int hostId() { + return Constants.NAV_HOST_ID; + } + + @Override + protected int routeId() { + return routeId; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + if(getArguments() != null){ + payDetail = (PayDetail) getArguments().getSerializable("payDetail"); + } + } + + @Override + public void onResume() { + super.onResume(); + setToolBarTitleWithBackIcon("KPay Refund"); + KPayViewModel.invalidAmountMsg.setValue(""); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + refundDisposable.dispose(); + retrieveUpdateDisposable.dispose(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + initViews(); + initData(); + setupRadioGroupListener(); + } + + private void initViews() { + radioGroupRefundType = mBinding.getRoot().findViewById(R.id.radio_group_refund_type); + radioOriginal = mBinding.getRoot().findViewById(R.id.radio_original); + radioPartial = mBinding.getRoot().findViewById(R.id.radio_partial); + etReferenceNo = mBinding.getRoot().findViewById(R.id.et_reference_no); + etOriginalAmount = mBinding.getRoot().findViewById(R.id.et_original_amount); + etRefundAmount = mBinding.getRoot().findViewById(R.id.et_refund_amount); + etRefundReason = mBinding.getRoot().findViewById(R.id.et_refund_reason); + originalAmountLayout = mBinding.getRoot().findViewById(R.id.original_amount_layout); + refundAmountLayout = mBinding.getRoot().findViewById(R.id.refund_amount_layout); + + // Set input filters for amount fields + etOriginalAmount.setFilters(new InputFilter[]{new DecimalDigitsInputFilter(11, 2)}); + etRefundAmount.setFilters(new InputFilter[]{new DecimalDigitsInputFilter(11, 2)}); + } + + private void initData() { + payDetail = TransactionUtil.getInstance().initWalletTransaction(TransactionsType.MMQR_REFUND); + payDetail.setInvoiceNo(SystemParamsOperation.getInstance().getIncrementInvoiceNum()); + + if(getArguments() != null){ + PayDetail passData = (PayDetail) getArguments().getSerializable("payDetail"); + if(passData != null && passData.getReferNo() != null){ + etReferenceNo.setText(passData.getReferNo()); + etReferenceNo.setEnabled(false); + } + } + } + + private void setupRadioGroupListener() { + radioGroupRefundType.setOnCheckedChangeListener((group, checkedId) -> { + if (checkedId == R.id.radio_partial) { + // Show both amount fields for partial refund + isPartialRefund = true; + originalAmountLayout.setVisibility(View.VISIBLE); + refundAmountLayout.setVisibility(View.VISIBLE); + } else { + // Hide amount fields for original refund + isPartialRefund = false; + originalAmountLayout.setVisibility(View.GONE); + refundAmountLayout.setVisibility(View.GONE); + } + }); + } + + private void processKPayRefund(String referenceNo, String refundAmount, String originalAmount, String reason) { + String merchantId = TransactionUtil.getInstance().getQRMerchantId(); + + // Generate unique refund request ID + String refundRequestId = referenceNo + "R"; + + showLoadingDialog("Processing refund..."); + + // Create KPay refund request + KPayRefund.RefundRequest refundRequest = KPayViewModel.createRefundRequest( + refundRequestId, + referenceNo, + merchantId, + refundAmount, + reason != null ? reason : "Refund request" + ); + + Disposable refundDi = KPayViewModel.kPayRefund(refundRequest) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + response -> { + dismissLoadingDialog(); + handleRefundResponse(response, referenceNo); + }, + throwable -> { + dismissLoadingDialog(); + LogUtil.e(TAG, "Refund error: " + throwable.getMessage()); + showDeclineDialog("Refund failed!\nCommunication Error!"); + ecrActionCancel("Refund failed"); + navigateToMain(); + }, + () -> LogUtil.d(TAG, "Refund request completed") + ); + + refundDisposable.add(refundDi); + } + + private void handleRefundResponse(KPayRefund.RefundResponse response, String referenceNo) { + if (response != null && response.getResponse() != null && "REFUND_SUCCESS".equalsIgnoreCase(response.getResponse().getRefundStatus())) { + LogUtil.d(TAG, "Refund successful!"); + + String refundAmount = response.getResponse().getRefundAmount(); + + String dateTime = SystemDateTime.getTodayDateFormat() + " " + SystemDateTime.getTodayTimeFormat(); + payDetail.setAmount(refundAmount == null ? 0 : POSUtil.getInstance().convertAmount(refundAmount)); + payDetail.setOriginalTransDate(dateTime); + payDetail.setQrTransStatus(1); + payDetail.setQrReferNo(referenceNo); + payDetail.setReferNo(referenceNo); + payDetail.setIsCanceled(true); + + retrievedUpdatePayDetail(referenceNo); + + } else { + LogUtil.d(TAG, "Refund failed!"); + payDetail.setQrTransStatus(-1); + payDetail.setQrReferNo(referenceNo); + payDetail.setReferNo(referenceNo); + + String errorMsg = "Refund failed"; + if (response != null && response.getResponse() != null && response.getResponse().getMsg() != null) { + errorMsg = response.getResponse().getMsg(); + } + payDetail.setTradeResultDes(errorMsg); + + sharedViewModel.payDetail.setValue(payDetail); + navigateToNext(); + } + } + + private void retrievedUpdatePayDetail(String refNum) { + LogUtil.d(TAG, "Trying to update Database!"); + retrieveUpdateDisposable.add(KPayViewModel.searchPayByRefNum(refNum) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(oldPay -> { + LogUtil.d(TAG, "Inside the subscribe!"); + if (oldPay != null) { + oldPay.setIsCanceled(true); + payDetail.setQrTransId(oldPay.getQrTransId()); + payDetail.setCustomerMobile(oldPay.getCustomerMobile()); + sharedViewModel.updatePayDetail(oldPay); + } + updateData(); + navigateToNext(); + }, + onError -> { + LogUtil.d(TAG, "On error Unable to retrieve PayDetail"); + updateData(); + navigateToNext(); + }, + () -> { + LogUtil.d(TAG, "No data found! navigating to Result Page!"); + updateData(); + navigateToNext(); + } + )); + } + + private void updateData() { + KPayViewModel.insertPayDetail(payDetail); + sharedViewModel.payDetail.postValue(payDetail); + } + + private void navigateToMain() { + routeId = R.id.action_QRRefundDetail_to_nav_main; + safeNavigateToRouteId(); + } + + private void navigateToNext() { + routeId = R.id.action_QRRefundDetail_to_transactionResultFragment; + safeNavigateToRouteId(); + } + + private void ecrActionCancel(String msg) { + if (sharedViewModel.isEcr.getValue() != null) { + if (sharedViewModel.isEcr.getValue()) { + sharedViewModel.isEcr.postValue(false); + CoreUtils.getInstance(sharedViewModel).responseRejectMsg(msg); + sharedViewModel.isEcrFinished.postValue(true); + } + } + } + + public class ClickEvent { + + public void onCancel() { + safePopBackStack(); + } + + public void onConfirm() { + KPayViewModel.invalidAmountMsg.setValue(""); + + String referenceNo = etReferenceNo.getText().toString().trim(); + String refundReason = etRefundReason.getText().toString().trim(); + + // Validate reference number + if (referenceNo.isEmpty()) { + KPayViewModel.invalidAmountMsg.setValue("Enter reference number"); + return; + } + + if (isPartialRefund) { + // Partial refund validation + String originalAmountStr = etOriginalAmount.getText().toString().trim(); + String refundAmountStr = etRefundAmount.getText().toString().trim(); + + if (originalAmountStr.isEmpty()) { + KPayViewModel.invalidAmountMsg.setValue("Enter original amount"); + return; + } + + if (refundAmountStr.isEmpty()) { + KPayViewModel.invalidAmountMsg.setValue("Enter refund amount"); + return; + } + + double originalAmount = Double.parseDouble(originalAmountStr); + double refundAmount = Double.parseDouble(refundAmountStr); + + if (originalAmount <= 0) { + KPayViewModel.invalidAmountMsg.setValue("Enter valid original amount"); + return; + } + + if (refundAmount <= 0) { + KPayViewModel.invalidAmountMsg.setValue("Enter valid refund amount"); + return; + } + + if (refundAmount > originalAmount) { + KPayViewModel.invalidAmountMsg.setValue("Refund amount cannot exceed original amount"); + return; + } + processKPayRefund(referenceNo, refundAmountStr, originalAmountStr, refundReason); + + } else { + // Original amount refund - no amount validation needed + processKPayRefund(referenceNo, "0", String.valueOf( etOriginalAmount.getText().toString().trim()), refundReason); + } + } + } + + public void navigateToPassword(){ + sharedViewModel.transactionsType.setValue(TransactionsType.MMQR_REFUND); + routeId = R.id.action_qrRefundPasswordFragment_to_inputPasswordFragment; + safeRouteTo(currentId,routeId,hostId); + } +} diff --git a/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRRefundFragment.java b/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRRefundFragment.java index c52b270..ef23d70 100644 --- a/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRRefundFragment.java +++ b/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRRefundFragment.java @@ -2,10 +2,13 @@ package com.utsmm.kbz.ui.qr_pay; import android.os.Bundle; import android.view.View; +import android.widget.EditText; +import android.widget.RadioGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -13,6 +16,7 @@ import com.utsmm.kbz.BR; import com.utsmm.kbz.R; import com.utsmm.kbz.config.Constants; import com.utsmm.kbz.ui.core_viewmodel.SharedViewModel; +import com.utsmm.kbz.ui.kpay.KPayViewModel; import com.utsmyanmar.baselib.fragment.DataBindingFragment; import com.utsmyanmar.baselib.util.DataBindingConfig; import com.utsmyanmar.paylibs.model.PayDetail; @@ -35,27 +39,33 @@ public class QRRefundFragment extends DataBindingFragment { } @Override protected int hostId() {return hostId;} - @Override protected int routeId() {return routeId;} @Override protected void initViewModel() { - sharedViewModel = getFragmentScopeViewModel(SharedViewModel.class); - qrPayViewModel = getFragmentScopeViewModel(QRPayViewModel.class); - + sharedViewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class); + qrPayViewModel = new ViewModelProvider(requireActivity()).get(QRPayViewModel.class); } @Override protected DataBindingConfig getDataBindingConfig() { - adapter = new QRRefundViewAdapter(); - return new DataBindingConfig(R.layout.fragment_qr_refund, BR.sharedViewModel, sharedViewModel).addBindingParam(BR.adapter, adapter); + adapter = new QRRefundViewAdapter(this::onRefundItemClicked); + return new DataBindingConfig(R.layout.fragment_qr_refund, BR.sharedViewModel, sharedViewModel) + .addBindingParam(BR.adapter, adapter) + .addBindingParam(BR.click, new ClickEvent()); } @Override public void onResume(){ super.onResume(); - setToolBarTitleWithBackIcon("Refund"); + setToolBarTitleWithBackIcon("QR Refund"); + } + + @Override + public void onCreate(@Nullable Bundle savedInstance){ + super.onCreate(savedInstance); + adapter = new QRRefundViewAdapter(this::onRefundItemClicked); } @Override @@ -73,12 +83,10 @@ public class QRRefundFragment extends DataBindingFragment { } private void updateList(List list){ - if(list == null || list.isEmpty()){ - showSingleInfoDialog("No data test"); - adapter.setData(null); - }else{ - adapter.setData(list); + if (adapter == null) { + return; } + adapter.setData(list); } public class ClickEvent { @@ -87,4 +95,10 @@ public class QRRefundFragment extends DataBindingFragment { } } + private void onRefundItemClicked(PayDetail payDetail) { + Bundle bundle = new Bundle(); + bundle.putSerializable("payDetail", payDetail); + routeId = R.id.action_qrRefundList_to_qrRefundDetail; + NavHostFragment.findNavController(this).navigate(R.id.action_qrRefundList_to_qrRefundDetail, bundle); + } } diff --git a/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRRefundViewAdapter.java b/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRRefundViewAdapter.java index 076cc45..19f5c3c 100644 --- a/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRRefundViewAdapter.java +++ b/app/src/main/java/com/utsmm/kbz/ui/qr_pay/QRRefundViewAdapter.java @@ -11,6 +11,7 @@ import android.view.ViewGroup; import com.utsmm.kbz.BR; import com.utsmm.kbz.R; import com.utsmm.kbz.databinding.ItemQrRefundBinding; +import com.utsmm.kbz.ui.adapters.QRPayAdapter; import com.utsmyanmar.paylibs.model.PayDetail; import java.util.ArrayList; @@ -20,16 +21,23 @@ import java.util.List; public class QRRefundViewAdapter extends RecyclerView.Adapter { private List payDetailList = new ArrayList<>(); + private final OnRefundItemClick listener; - public QRRefundViewAdapter(){} - public QRRefundViewAdapter(List payDetailList){ - this.payDetailList = payDetailList; + public interface OnRefundItemClick { + void onClick(PayDetail payDetail); } + public QRRefundViewAdapter(OnRefundItemClick listener){ + this.listener = listener; + } +// public QRRefundViewAdapter(List payDetailList, OnRefundItemClick listener){ +// this.payDetailList = payDetailList; +// this.listener = listener; +// } + + public void setData(List data){ - if(data == null) return; - payDetailList.clear(); - payDetailList.addAll(data); + this.payDetailList = data != null ? data : new ArrayList<>(); notifyDataSetChanged(); } @@ -49,7 +57,7 @@ public class QRRefundViewAdapter extends RecyclerView.Adapter { + if(listener != null) listener.onClick(payDetail); + }); } } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_qr_refund_detail.xml b/app/src/main/res/layout/fragment_qr_refund_detail.xml new file mode 100644 index 0000000..1f6587f --- /dev/null +++ b/app/src/main/res/layout/fragment_qr_refund_detail.xml @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +