diff --git a/app/src/main/java/com/utsmm/kbz/MainActivity.java b/app/src/main/java/com/utsmm/kbz/MainActivity.java index 016c046..09657ff 100644 --- a/app/src/main/java/com/utsmm/kbz/MainActivity.java +++ b/app/src/main/java/com/utsmm/kbz/MainActivity.java @@ -34,6 +34,7 @@ import com.utsmyanmar.baselib.ui.AnimationDialog; import com.utsmyanmar.checkxread.sdk.NexGoSDK; import com.utsmyanmar.ecr.ECRHelper; import com.utsmyanmar.paylibs.Constant; +import com.utsmyanmar.paylibs.model.PayDetail; import com.utsmyanmar.paylibs.print.printx.PrintXReceipt; import com.utsmyanmar.paylibs.utils.POSUtil; import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation; @@ -51,6 +52,7 @@ import com.utsmm.kbz.util.tms.TMSUtil; import java.util.Calendar; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -408,17 +410,55 @@ public class MainActivity extends AppCompatActivity implements private void handleAutoSettlementIntent(Intent intent) { if (intent == null) return; + + // Handle regular auto settlement boolean auto = intent.getBooleanExtra("AUTO_SETTLEMENT", false); - if (!auto) return; + if (auto) { + com.utsmyanmar.paylibs.model.PayDetail payDetail = (com.utsmyanmar.paylibs.model.PayDetail) intent.getSerializableExtra("EXTRA_PAY_DETAIL"); + if (payDetail != null) { + sharedViewModel.payDetail.setValue(payDetail); + sharedViewModel.transactionsType.setValue(com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.SETTLEMENT); + try { + navController.navigate(R.id.transactionResultFragment); + } catch (Exception e) { + LogUtil.e(TAG, "Navigation error: " + e.getMessage()); + } + } + return; + } + + // Handle QR auto settlement + boolean autoQR = intent.getBooleanExtra("AUTO_QR_SETTLEMENT", false); + if (autoQR) { + int qrSaleCount = intent.getIntExtra("QR_SALE_COUNT", 0); + long qrSaleAmount = intent.getLongExtra("QR_SALE_AMOUNT", 0L); + int qrRefundCount = intent.getIntExtra("QR_REFUND_COUNT", 0); + long qrRefundAmount = intent.getLongExtra("QR_REFUND_AMOUNT", 0L); + long totalAmount = intent.getLongExtra("QR_TOTAL_AMOUNT", 0L); + List qrTransList = (List) intent.getSerializableExtra("QR_TRANS_LIST"); - com.utsmyanmar.paylibs.model.PayDetail payDetail = (com.utsmyanmar.paylibs.model.PayDetail) intent.getSerializableExtra("EXTRA_PAY_DETAIL"); - if (payDetail != null) { + // Create QR settlement PayDetail + com.utsmyanmar.paylibs.model.SettleData settleData = new com.utsmyanmar.paylibs.model.SettleData( + qrSaleCount, qrSaleAmount, 0, 0L, qrRefundCount, qrRefundAmount, 0, 0L); + + com.utsmyanmar.paylibs.model.TradeData tradeData = com.utsmyanmar.paylibs.utils.params.Params.newTrade(false); + com.utsmyanmar.paylibs.model.PayDetail payDetail = tradeData.getPayDetail(); + payDetail.setSettleDataObj(settleData); + payDetail.setTransactionType(com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.MMQR_SETTLEMENT.value); + payDetail.setTransType(com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.MMQR_SETTLEMENT.name); + payDetail.setAmount(totalAmount); + payDetail.setTradeAnswerCode("000"); + + // Note: QR transactions list is not passed to avoid serialization issues + // The settlement summary is sufficient for the receipt + sharedViewModel.payDetails.setValue(qrTransList); sharedViewModel.payDetail.setValue(payDetail); - sharedViewModel.transactionsType.setValue(com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.SETTLEMENT); + sharedViewModel.transactionsType.setValue(com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.MMQR_SETTLEMENT); + try { navController.navigate(R.id.transactionResultFragment); } catch (Exception e) { - LogUtil.e(TAG, "Navigation error: " + e.getMessage()); + LogUtil.e(TAG, "QR Auto Settlement navigation error: " + e.getMessage()); } } } diff --git a/app/src/main/java/com/utsmm/kbz/MainFragment.java b/app/src/main/java/com/utsmm/kbz/MainFragment.java index 913b17b..d8d621e 100644 --- a/app/src/main/java/com/utsmm/kbz/MainFragment.java +++ b/app/src/main/java/com/utsmm/kbz/MainFragment.java @@ -147,7 +147,7 @@ public class MainFragment extends DataBindingFragment { delayFunctionCall(()-> { NexGoSDK.getInstance().cancelCheckCard(); NexGoSDK.getInstance().closeReader(); - disableHomeButton(); + enableHomeButton(); disableTaskButton(); BaseApplication.getInstance().deviceEngine.getPlatform().hideNavigationBar(); }); @@ -196,14 +196,8 @@ public class MainFragment extends DataBindingFragment { - - } - - - - private void checkDownload() { Log.d(TAG, "Calling from Check download: "); if (!SystemParamsOperation.getInstance().isDownloadedParams() && SystemParamsOperation.getInstance().isActive() && tmsProcessViewModel.getIsCalled().getValue() == null) { @@ -243,7 +237,7 @@ public class MainFragment extends DataBindingFragment { sharedViewModel.setManualEntryStatus(SystemParamsOperation.getInstance().getManualEntryStatus()); -// generateMockQR(); + generateMockQR(); } /* @@ -830,15 +824,15 @@ public class MainFragment extends DataBindingFragment { } -// private void generateMockQR() { -// String transDate = "23/12/25"; -// String transTime = "12:06:30"; -// TradeData tradeData = MockData.getInstance().generateMockDataWithTime(TransactionsType.MMQR, 1,transDate,transTime); -// PayDetail payDetail = tradeData.getPayDetail(); -// sharedViewModel.insertPayDetail(payDetail); -// LogUtil.d(TAG,"------------------- Inserted Mocked Data ------------------"); -// LogUtil.d(TAG,"trans date : "+payDetail.getTransDate() +"- Trans Time: "+payDetail.getTransTime()); -// } + private void generateMockQR() { + String transDate = "24/12/25"; + String transTime = "12:06:30"; + TradeData tradeData = MockData.getInstance().generateMockDataWithTime(TransactionsType.MMQR, 1,transDate,transTime); + PayDetail payDetail = tradeData.getPayDetail(); + sharedViewModel.insertPayDetail(payDetail); + LogUtil.d(TAG,"------------------- Inserted Mocked Data ------------------"); + LogUtil.d(TAG,"trans date : "+payDetail.getTransDate() +"- Trans Time: "+payDetail.getTransTime()); + } public class ClickEvent { diff --git a/app/src/main/java/com/utsmm/kbz/service/AutoAlarmReceiver.java b/app/src/main/java/com/utsmm/kbz/service/AutoAlarmReceiver.java index 8a69476..aa41c45 100644 --- a/app/src/main/java/com/utsmm/kbz/service/AutoAlarmReceiver.java +++ b/app/src/main/java/com/utsmm/kbz/service/AutoAlarmReceiver.java @@ -1,10 +1,15 @@ package com.utsmm.kbz.service; +import android.app.AlarmManager; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import com.utsmyanmar.paylibs.utils.LogUtil; +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation; + +import java.util.Calendar; public class AutoAlarmReceiver extends BroadcastReceiver { @@ -12,17 +17,84 @@ public class AutoAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - LogUtil.d(TAG, "AutoAlarmReceiver triggered at: " + new java.util.Date().toString()); - boolean isTestAlarm = intent.getBooleanExtra("TEST_ALARM", false); - if (isTestAlarm) { - LogUtil.d(TAG, "This is a test alarm - AutoAlarmReceiver is working correctly!"); + scheduleNextDayAlarm(context); + try { + Intent serviceIntent = new Intent(context, AutoSettleService.class); + context.startService(serviceIntent); + LogUtil.d(TAG, "AutoSettleService started successfully"); + } catch (Exception e) { + LogUtil.e(TAG, "Failed to start AutoSettleService: " + e.getMessage()); + e.printStackTrace(); } + } - // Start the service to send the API request - Intent serviceIntent = new Intent(context, AutoSettleService.class); - context.startService(serviceIntent); - - LogUtil.d(TAG, "AutoSettleService started"); + private void scheduleNextDayAlarm(Context context) { + try { + String timeStr = SystemParamsOperation.getInstance().getClearBatchTime(); + LogUtil.d(TAG, " Rescheduling next day's alarm for timeStr: " + timeStr); + + if (timeStr == null || timeStr.trim().isEmpty()) { + LogUtil.d(TAG, "Clear batch time is empty, cannot reschedule"); + return; + } + + String[] parts = timeStr.trim().split(":"); + if (parts.length != 2) { + LogUtil.d(TAG, "Invalid time format: " + timeStr); + return; + } + + int hour = Integer.parseInt(parts[0]); + int minute = Integer.parseInt(parts[1]); + + // Validate time range + if (hour < 0 || hour > 23 || minute < 0 || minute > 59) { + LogUtil.d(TAG, "Invalid time values - hour: " + hour + ", minute: " + minute); + return; + } + + // Set up next day's alarm + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DAY_OF_YEAR, 1); // Next day + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + + long nextTriggerTime = calendar.getTimeInMillis(); + + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + if (alarmManager == null) { + LogUtil.e(TAG, "AlarmManager is null, cannot reschedule"); + return; + } + + Intent intent = new Intent(context, AutoAlarmReceiver.class); + PendingIntent pendingIntent = PendingIntent.getBroadcast( + context, + 1001, + intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE + ); + + // Schedule next day's alarm + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + if (alarmManager.canScheduleExactAlarms()) { + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent); + } else { + alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent); + } + } else { + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent); + } + } else { + alarmManager.setExact(AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent); + } + + } catch (Exception e) { + e.printStackTrace(); + } } } diff --git a/app/src/main/java/com/utsmm/kbz/service/AutoSettleService.java b/app/src/main/java/com/utsmm/kbz/service/AutoSettleService.java index 83ebead..c90ba5c 100644 --- a/app/src/main/java/com/utsmm/kbz/service/AutoSettleService.java +++ b/app/src/main/java/com/utsmm/kbz/service/AutoSettleService.java @@ -11,6 +11,7 @@ import android.text.TextUtils; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; +import androidx.lifecycle.Observer; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.utsmyanmar.baselib.repo.Repository; @@ -20,7 +21,6 @@ import com.utsmm.kbz.ui.settlement.SettlementViewModel; import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; import com.utsmyanmar.paylibs.isobuilder.builderx.ISOMsgX; @@ -35,28 +35,34 @@ public class AutoSettleService extends Service { public static final String ACTION_DATA_RECEIVED = BuildConfig.APPLICATION_ID + ".ACTION_DATA_RECEIVED"; public static final String EXTRA_DATA = BuildConfig.APPLICATION_ID + ".EXTRA_DATA"; - private Handler handler; - - private SettlementViewModel settlementViewModel; + + // Flags to prevent infinite loops + private boolean regularSettlementCompleted = false; + private boolean qrSettlementCompleted = false; + + // Observers to keep reference for cleanup + private Observer> regularSettlementObserver; + private Observer> qrSettlementObserver; @Inject Repository repository; public static final String NOTIFICATION_CHANNEL_ID = "10001"; private final static String default_notification_channel_id = "default"; + private void createNotification() { NotificationManager mNotificationManager = (NotificationManager)getSystemService( NOTIFICATION_SERVICE ) ; NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getApplicationContext() , default_notification_channel_id ) ; - mBuilder.setContentTitle( "Alarm manager is running!" ) ; - mBuilder.setContentText( "Wiping out the transactions!" ) ; - mBuilder.setTicker( "Notification Listener Service Example" ) ; + mBuilder.setContentTitle( "Auto Settlement Running" ) ; + mBuilder.setContentText( "Processing transactions..." ) ; + mBuilder.setTicker( "Auto Settlement Service" ) ; mBuilder.setSmallIcon(R.drawable. ic_launcher_foreground ) ; mBuilder.setAutoCancel( true ) ; if (android.os.Build.VERSION. SDK_INT >= android.os.Build.VERSION_CODES. O ) { int importance = NotificationManager. IMPORTANCE_HIGH ; - NotificationChannel notificationChannel = new NotificationChannel( NOTIFICATION_CHANNEL_ID , "NOTIFICATION_CHANNEL_NAME" , importance) ; + NotificationChannel notificationChannel = new NotificationChannel( NOTIFICATION_CHANNEL_ID , "AUTO_SETTLEMENT_CHANNEL" , importance) ; mBuilder.setChannelId( NOTIFICATION_CHANNEL_ID ) ; assert mNotificationManager != null; mNotificationManager.createNotificationChannel(notificationChannel) ; @@ -74,15 +80,16 @@ public class AutoSettleService extends Service { @SuppressLint("CheckResult") @Override public int onStartCommand(Intent intent, int flags, int startId) { - // Perform the Settlement request here LogUtil.d(TAG, "<<<<<<<<<<<<<<< Auto Settlement Service Started >>>>>>>>>>>>>>>>."); try { - sendDataToViewModel("Hello there"); + sendDataToViewModel("Auto settlement initiated"); createNotification(); if (SystemParamsOperation.getInstance().getSettlementStatus()) { - performSettlement(); +// performSettlement(); + regularSettlementCompleted = true; + performQRSettlement(); } else { LogUtil.d(TAG, "Settlement is disabled in system parameters"); stopSelf(); @@ -97,175 +104,315 @@ public class AutoSettleService extends Service { } private void performSettlement() { + if (regularSettlementCompleted) { + return; + } + final ISOMsgX isoMsgX = new ISOMsgX.ISOMsgXBuilder( com.utsmyanmar.paylibs.isobuilder.builderx.ISOVersion.VERSION_1993, com.utsmyanmar.paylibs.isobuilder.ISOMode.BOTH_HEADER_TPDU, com.utsmyanmar.paylibs.utils.enums.HostName.BPC ).build(); - repository.getSettlementPOS().observeForever(list -> { - int saleCount = 0; - long saleAmount = 0L; - int preCount = 0; - long preAmount = 0L; - int refundCount = 0; - long refundAmount = 0L; - int caCount = 0; - long caAmount = 0L; + regularSettlementObserver = new Observer>() { + @Override + public void onChanged(java.util.List list) { + if (regularSettlementCompleted) { + return; + } + regularSettlementCompleted = true; + + // Remove observer immediately to prevent infinite loop + repository.getSettlementPOS().removeObserver(this); - if (list != null && !list.isEmpty()) { - for (com.utsmyanmar.paylibs.model.PayDetail pay : list) { - if (pay.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionType.SALE && !pay.isCanceled) { - saleCount++; - saleAmount += pay.getAmount(); - } else if (pay.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionType.PRE_SALE_COMPLETE && !pay.isCanceled) { - preCount++; - preAmount += pay.getAmount(); - } else if (pay.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionType.REFUND) { - refundCount++; - refundAmount += pay.getAmount(); - } else if (pay.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionType.CASH_ADVANCE) { - caCount++; - caAmount += pay.getAmount(); + int saleCount = 0; + long saleAmount = 0L; + int preCount = 0; + long preAmount = 0L; + int refundCount = 0; + long refundAmount = 0L; + int caCount = 0; + long caAmount = 0L; + + if (list != null && !list.isEmpty()) { + for (com.utsmyanmar.paylibs.model.PayDetail pay : list) { + if (pay.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionType.SALE && !pay.isCanceled) { + saleCount++; + saleAmount += pay.getAmount(); + } else if (pay.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionType.PRE_SALE_COMPLETE && !pay.isCanceled) { + preCount++; + preAmount += pay.getAmount(); + } else if (pay.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionType.REFUND) { + refundCount++; + refundAmount += pay.getAmount(); + } else if (pay.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionType.CASH_ADVANCE) { + caCount++; + caAmount += pay.getAmount(); + } } } - } - com.utsmyanmar.paylibs.model.TradeData tradeData = com.utsmyanmar.paylibs.utils.params.Params.newTrade(true); - com.utsmyanmar.paylibs.model.PayDetail payDetail = tradeData.getPayDetail(); + com.utsmyanmar.paylibs.model.TradeData tradeData = com.utsmyanmar.paylibs.utils.params.Params.newTrade(true); + com.utsmyanmar.paylibs.model.PayDetail payDetail = tradeData.getPayDetail(); - String bitmap = com.utsmyanmar.paylibs.utils.iso_utils.BitmapConfig.BPC_SETTLEMENT; - payDetail.setTransType(com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.SETTLEMENT.name); - payDetail.setTransactionType(com.utsmyanmar.paylibs.utils.iso_utils.TransactionType.SETTLEMENT); - payDetail.setProcessCode(com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.SETTLEMENT.processCode); - payDetail.setBatchNo(com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation.getInstance().getCurrentBatchNum()); + String bitmap = com.utsmyanmar.paylibs.utils.iso_utils.BitmapConfig.BPC_SETTLEMENT; + payDetail.setTransType(com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.SETTLEMENT.name); + payDetail.setTransactionType(com.utsmyanmar.paylibs.utils.iso_utils.TransactionType.SETTLEMENT); + payDetail.setProcessCode(com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.SETTLEMENT.processCode); + payDetail.setBatchNo(com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation.getInstance().getCurrentBatchNum()); - com.utsmyanmar.paylibs.model.SettleData settleData = new com.utsmyanmar.paylibs.model.SettleData( - saleCount, - saleAmount, - preCount, - preAmount, - refundCount, - refundAmount, - caCount, - caAmount - ); - payDetail.setSettleDataObj(settleData); + com.utsmyanmar.paylibs.model.SettleData settleData = new com.utsmyanmar.paylibs.model.SettleData( + saleCount, saleAmount, preCount, preAmount, refundCount, refundAmount, caCount, caAmount); + payDetail.setSettleDataObj(settleData); - long totalAmount = saleAmount + preAmount + refundAmount + caAmount; - String settlementData; - if (refundAmount != 0L) { - long creditTotal = saleAmount + preAmount + caAmount; - long subTotal = creditTotal - refundAmount; - if (subTotal < 0L) { - settlementData = "D" + String.format(java.util.Locale.getDefault(), "%012d", Math.abs(subTotal)); + long totalAmount = saleAmount + preAmount + refundAmount + caAmount; + String settlementData; + if (refundAmount != 0L) { + long creditTotal = saleAmount + preAmount + caAmount; + long subTotal = creditTotal - refundAmount; + if (subTotal < 0L) { + settlementData = "D" + String.format(java.util.Locale.getDefault(), "%012d", Math.abs(subTotal)); + } else { + settlementData = "C" + String.format(java.util.Locale.getDefault(), "%012d", subTotal); + } } else { - settlementData = "C" + String.format(java.util.Locale.getDefault(), "%012d", subTotal); + settlementData = "C" + String.format(java.util.Locale.getDefault(), "%012d", totalAmount); } - } else { - settlementData = "C" + String.format(java.util.Locale.getDefault(), "%012d", totalAmount); - } - payDetail.setSettleData(settlementData); - payDetail.setAmount(totalAmount); + payDetail.setSettleData(settlementData); + payDetail.setAmount(totalAmount); - tradeData.setPayDetail(payDetail); - tradeData.setField60(com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation.getInstance().getCurrentBatchNum()); + tradeData.setPayDetail(payDetail); + tradeData.setField60(com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation.getInstance().getCurrentBatchNum()); + + byte[] sendBytes = isoMsgX.buildISOPackets(tradeData, bitmap, com.utsmyanmar.paylibs.utils.MessageType.SETTLEMENT); + com.utsmyanmar.paylibs.network.ISOSocket.getInstance().enqueue(sendBytes, sendBytes.length, false, new com.utsmyanmar.paylibs.network.ISOCallback() { + @Override + public void onReceive(byte[] bytes, int length) { + java.util.Map responseMap = isoMsgX.parseISOPackets(bytes, length); + if (responseMap != null) { + String resultStr = ""; + try { + resultStr = responseMap.get("F039").getDataStr(); + } catch (NullPointerException e) { + e.printStackTrace(); + payDetail.setIsNeedReversal(true); + return; + } + payDetail.setTradeAnswerCode(resultStr); + if (TextUtils.equals(resultStr, com.utsmyanmar.paylibs.Constant.ANSWER_CODE_ACCEPT) || TextUtils.equals(resultStr, com.utsmyanmar.paylibs.Constant.ANSWER_CODE_APPROVED)) { + payDetail.setIsNeedReversal(false); + } else if (TextUtils.equals(resultStr, "95") || TextUtils.equals(resultStr, "095")) { + payDetail.setIsNeedReversal(true); + } + } + } + + @Override + public void onError(String msg) { + com.utsmyanmar.paylibs.network.ISOSocket.getInstance().switchIp(); + com.utsmyanmar.paylibs.network.ISOSocket.getInstance().enqueue(sendBytes, sendBytes.length, false, this); + } + + @Override + public void onComplete() { + LogUtil.d(TAG, "Auto settlement transaction completed successfully"); + + if (list != null && !list.isEmpty()) { + for (com.utsmyanmar.paylibs.model.PayDetail p : list) { + repository.deletePayDetail(p); + } + } + repository.insertPayDetail(payDetail); - byte[] sendBytes = isoMsgX.buildISOPackets(tradeData, bitmap, com.utsmyanmar.paylibs.utils.MessageType.SETTLEMENT); - com.utsmyanmar.paylibs.network.ISOSocket.getInstance().enqueue(sendBytes, sendBytes.length, false, new com.utsmyanmar.paylibs.network.ISOCallback() { - @Override - public void onReceive(byte[] bytes, int length) { - java.util.Map responseMap = isoMsgX.parseISOPackets(bytes, length); - if (responseMap != null) { - String resultStr = ""; try { - resultStr = responseMap.get("F039").getDataStr(); - } catch (NullPointerException e) { + com.utsmyanmar.paylibs.model.PayDetail cleanPayDetail = new com.utsmyanmar.paylibs.model.PayDetail(); + cleanPayDetail.setTransType(payDetail.getTransType()); + cleanPayDetail.setTransactionType(payDetail.getTransactionType()); + cleanPayDetail.setProcessCode(payDetail.getProcessCode()); + cleanPayDetail.setBatchNo(payDetail.getBatchNo()); + cleanPayDetail.setSettleData(payDetail.getSettleData()); + cleanPayDetail.setAmount(payDetail.getAmount()); + cleanPayDetail.setTradeAnswerCode(payDetail.getTradeAnswerCode()); + cleanPayDetail.setIsNeedReversal(payDetail.getIsNeedReversal()); + + Intent uiIntent = new Intent(getApplicationContext(), com.utsmm.kbz.MainActivity.class); + uiIntent.putExtra("AUTO_SETTLEMENT", true); + uiIntent.putExtra("EXTRA_TRANSACTION_TYPE", com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.SETTLEMENT.value); + uiIntent.putExtra("EXTRA_PAY_DETAIL", cleanPayDetail); + uiIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(uiIntent); + + LogUtil.d(TAG, "MainActivity started successfully with auto settlement result"); + + } catch (Exception e) { + LogUtil.e(TAG, "Error starting MainActivity: " + e.getMessage()); e.printStackTrace(); - payDetail.setIsNeedReversal(true); - return; - } - payDetail.setTradeAnswerCode(resultStr); - if (TextUtils.equals(resultStr, com.utsmyanmar.paylibs.Constant.ANSWER_CODE_ACCEPT) || TextUtils.equals(resultStr, com.utsmyanmar.paylibs.Constant.ANSWER_CODE_APPROVED)) { - payDetail.setIsNeedReversal(false); - } else if (TextUtils.equals(resultStr, "95") || TextUtils.equals(resultStr, "095")) { - payDetail.setIsNeedReversal(true); + + try { + Intent fallbackIntent = new Intent(getApplicationContext(), com.utsmm.kbz.MainActivity.class); + fallbackIntent.putExtra("AUTO_SETTLEMENT", true); + fallbackIntent.putExtra("EXTRA_TRANSACTION_TYPE", com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.SETTLEMENT.value); + fallbackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(fallbackIntent); + + LogUtil.d(TAG, "MainActivity started with fallback approach"); + + } catch (Exception fallbackException) { + LogUtil.e(TAG, "Fallback also failed: " + fallbackException.getMessage()); + } } + + checkIfBothSettlementsComplete(); } + }); + } + }; + + repository.getSettlementPOS().observeForever(regularSettlementObserver); + } + + private void performQRSettlement() { + if (qrSettlementCompleted) { + return; + } + + qrSettlementObserver = new Observer>() { + @Override + public void onChanged(java.util.List payDetailList) { + if (qrSettlementCompleted) { + return; } - - @Override - public void onError(String msg) { - com.utsmyanmar.paylibs.network.ISOSocket.getInstance().switchIp(); - com.utsmyanmar.paylibs.network.ISOSocket.getInstance().enqueue(sendBytes, sendBytes.length, false, this); - } - - @Override - public void onComplete() { - LogUtil.d(TAG, "Auto settlement transaction completed successfully"); - - if (list != null && !list.isEmpty()) { - for (com.utsmyanmar.paylibs.model.PayDetail p : list) { - repository.deletePayDetail(p); - } - } - repository.insertPayDetail(payDetail); - + qrSettlementCompleted = true; + + // Remove observer immediately to prevent infinite loop + repository.getTransactionHistory().removeObserver(this); + + if (payDetailList != null) { + int qrSaleCount = 0; + long qrSaleAmount = 0; + int qrRefundCount = 0; + long qrRefundAmount = 0; + + java.util.ArrayList qrTransactionsList = new java.util.ArrayList<>(); + java.util.ArrayList qrTransListAll = new java.util.ArrayList<>(); + try { - // Remove the non-serializable SettleData object before passing through Intent - // Create a clean copy of PayDetail without the problematic SettleData object - com.utsmyanmar.paylibs.model.PayDetail cleanPayDetail = new com.utsmyanmar.paylibs.model.PayDetail(); - cleanPayDetail.setTransType(payDetail.getTransType()); - cleanPayDetail.setTransactionType(payDetail.getTransactionType()); - cleanPayDetail.setProcessCode(payDetail.getProcessCode()); - cleanPayDetail.setBatchNo(payDetail.getBatchNo()); - cleanPayDetail.setSettleData(payDetail.getSettleData()); - cleanPayDetail.setAmount(payDetail.getAmount()); - cleanPayDetail.setTradeAnswerCode(payDetail.getTradeAnswerCode()); - cleanPayDetail.setIsNeedReversal(payDetail.getIsNeedReversal()); - // Note: Not setting SettleDataObj as it's not serializable + for (com.utsmyanmar.paylibs.model.PayDetail payDetail : payDetailList) { + // Filter for successful QR transactions only + if ((payDetail.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.MMQR.value + || payDetail.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.MMQR_REFUND.value) + && payDetail.getQrTransStatus() == 1) { - Intent uiIntent = new Intent(getApplicationContext(), com.utsmm.kbz.MainActivity.class); - uiIntent.putExtra("AUTO_SETTLEMENT", true); - uiIntent.putExtra("EXTRA_TRANSACTION_TYPE", com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.SETTLEMENT.value); - uiIntent.putExtra("EXTRA_PAY_DETAIL", cleanPayDetail); - uiIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - startActivity(uiIntent); - - LogUtil.d(TAG, "MainActivity started successfully with auto settlement result"); - - } catch (Exception e) { - LogUtil.e(TAG, "Error starting MainActivity: " + e.getMessage()); - e.printStackTrace(); - - // Fallback: start MainActivity without the PayDetail extra - try { - Intent fallbackIntent = new Intent(getApplicationContext(), com.utsmm.kbz.MainActivity.class); - fallbackIntent.putExtra("AUTO_SETTLEMENT", true); - fallbackIntent.putExtra("EXTRA_TRANSACTION_TYPE", com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.SETTLEMENT.value); - fallbackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - startActivity(fallbackIntent); - - LogUtil.d(TAG, "MainActivity started with fallback approach"); - - } catch (Exception fallbackException) { - LogUtil.e(TAG, "Fallback also failed: " + fallbackException.getMessage()); + qrTransactionsList.add(payDetail); + + if (payDetail.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.MMQR.value) { + qrSaleCount++; + qrSaleAmount += payDetail.getAmount(); + } else if (payDetail.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.MMQR_REFUND.value) { + qrRefundCount++; + qrRefundAmount += payDetail.getAmount(); + } + } else if (payDetail.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.MMQR.value + || payDetail.getTransactionType() == com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.MMQR_REFUND.value) { + // Include all QR transactions for deletion (successful and failed) + qrTransListAll.add(payDetail); + } } + } catch (IllegalStateException e) { + LogUtil.e(TAG, "QR Auto Settlement: Database cursor error - likely due to large data size: " + e); + checkIfBothSettlementsComplete(); + return; } - // Stop the service - stopSelf(); + if (!qrTransactionsList.isEmpty()) { + LogUtil.d(TAG, "Processing QR Auto Settlement with " + qrTransactionsList.size() + " transactions"); + + long totalAmount = qrSaleAmount - qrRefundAmount; + + // Increment batch number for QR settlement + SystemParamsOperation.getInstance().getIncrementBatchNo(); + + // Create settlement data for QR transactions + com.utsmyanmar.paylibs.model.SettleData settleData = new com.utsmyanmar.paylibs.model.SettleData( + qrSaleCount, qrSaleAmount, 0, 0L, qrRefundCount, qrRefundAmount, 0, 0L); + + com.utsmyanmar.paylibs.model.TradeData tradeData = com.utsmyanmar.paylibs.utils.params.Params.newTrade(false); + com.utsmyanmar.paylibs.model.PayDetail payDetail = tradeData.getPayDetail(); + payDetail.setSettleDataObj(settleData); + payDetail.setTransactionType(com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.MMQR_SETTLEMENT.value); + payDetail.setTransType(com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.MMQR_SETTLEMENT.name); + payDetail.setAmount(totalAmount); + payDetail.setTradeAnswerCode("000"); + payDetail.setBatchNo(SystemParamsOperation.getInstance().getCurrentBatchNum()); + + // Insert QR settlement record + repository.insertPayDetail(payDetail); + + // Delete settled QR transactions + for (com.utsmyanmar.paylibs.model.PayDetail pay : qrTransactionsList) { + repository.deletePayDetail(pay); + } + + // Delete all other QR transactions (failed ones) + for (com.utsmyanmar.paylibs.model.PayDetail pay : qrTransListAll) { + repository.deletePayDetail(pay); + } + + // Push e-receipt data + try { + com.utsmyanmar.baselib.network.model.e_receipt.EReceiptRequest request = + com.utsmm.kbz.util.EReceiptUtil.getInstance().generateQRSettlement(payDetail); + LogUtil.d(TAG, "QR Settlement e-receipt data prepared"); + } catch (Exception e) { + LogUtil.e(TAG, "Error preparing QR settlement e-receipt: " + e.getMessage()); + } + + try { + // Start MainActivity with QR settlement data for automatic printing + Intent uiIntent = new Intent(getApplicationContext(), com.utsmm.kbz.MainActivity.class); + uiIntent.putExtra("AUTO_QR_SETTLEMENT", true); + uiIntent.putExtra("EXTRA_TRANSACTION_TYPE", com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.MMQR_SETTLEMENT.value); + uiIntent.putExtra("QR_SALE_COUNT", qrSaleCount); + uiIntent.putExtra("QR_SALE_AMOUNT", qrSaleAmount); + uiIntent.putExtra("QR_REFUND_COUNT", qrRefundCount); + uiIntent.putExtra("QR_REFUND_AMOUNT", qrRefundAmount); + uiIntent.putExtra("QR_TOTAL_AMOUNT", totalAmount); + uiIntent.putExtra("QR_TRANS_LIST", qrTransactionsList); + uiIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(uiIntent); + + LogUtil.d(TAG, "MainActivity started successfully with QR auto settlement result"); + + } catch (Exception e) { + LogUtil.e(TAG, "Error starting MainActivity for QR settlement: " + e.getMessage()); + e.printStackTrace(); + } + + LogUtil.d(TAG, "QR Auto Settlement completed successfully"); + } else { + LogUtil.d(TAG, "No QR transactions found for auto settlement"); + } } - }); - }); + + checkIfBothSettlementsComplete(); + } + }; + + repository.getTransactionHistory().observeForever(qrSettlementObserver); + } + + private void checkIfBothSettlementsComplete() { + // Stop service only when both settlements are processed (or skipped if no data) + if (regularSettlementCompleted && qrSettlementCompleted) { + LogUtil.d(TAG, "Both settlements completed, stopping service"); + stopSelf(); + } } @Override public void onCreate() { super.onCreate(); handler = new Handler(); - - } @Nullable @@ -278,4 +425,19 @@ public class AutoSettleService extends Service { public boolean onUnbind(Intent intent) { return super.onUnbind(intent); } -} + + @Override + public void onDestroy() { + super.onDestroy(); + + // Clean up observers to prevent memory leaks + if (regularSettlementObserver != null && repository != null) { + repository.getSettlementPOS().removeObserver(regularSettlementObserver); + } + if (qrSettlementObserver != null && repository != null) { + repository.getTransactionHistory().removeObserver(qrSettlementObserver); + } + + LogUtil.d(TAG, "AutoSettleService destroyed and cleaned up"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/utsmm/kbz/ui/core_ui/TransactionResultFragment.java b/app/src/main/java/com/utsmm/kbz/ui/core_ui/TransactionResultFragment.java index 612c33d..1a76b28 100644 --- a/app/src/main/java/com/utsmm/kbz/ui/core_ui/TransactionResultFragment.java +++ b/app/src/main/java/com/utsmm/kbz/ui/core_ui/TransactionResultFragment.java @@ -113,7 +113,8 @@ public class TransactionResultFragment extends DataBindingFragment implements Da } if (resultStr.equals(Constant.ANSWER_CODE_ACCEPT) || resultStr.equals(Constant.ANSWER_CODE_APPROVED)) { /* ISO 8583 RESPONSE */ - if(sharedViewModel.payDetail.getValue().getTransactionType() != TransactionsType.SETTLEMENT.value) { + if(sharedViewModel.payDetail.getValue().getTransactionType() != TransactionsType.SETTLEMENT.value + && sharedViewModel.payDetail.getValue().getTransactionType() != TransactionsType.MMQR_SETTLEMENT.value) { if(SystemParamsOperation.getInstance().isAlertSound()) { startSound(getResourceString(R.string.txt_audio_thank_you)); diff --git a/app/src/main/java/com/utsmm/kbz/ui/tms/TMSProcessFragment.java b/app/src/main/java/com/utsmm/kbz/ui/tms/TMSProcessFragment.java index 8b01184..7f9700b 100644 --- a/app/src/main/java/com/utsmm/kbz/ui/tms/TMSProcessFragment.java +++ b/app/src/main/java/com/utsmm/kbz/ui/tms/TMSProcessFragment.java @@ -32,6 +32,7 @@ import com.utsmm.kbz.util.tms.TMSUtil; import java.io.IOException; import java.util.Calendar; +import java.util.Date; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -219,7 +220,7 @@ public class TMSProcessFragment extends DataBindingFragment { CurrencyType currencyType = SystemParamsOperation.getInstance().getCurrencyType(); sharedViewModel.set_currencyText(currencyType.name); // tmsProcessViewModel.loadEmvParameters(); -// scheduleAutoSettlement(); + scheduleAutoSettlement(); navigateToMain(); } }); @@ -278,11 +279,13 @@ public class TMSProcessFragment extends DataBindingFragment { LogUtil.d(TAG, "Current time: " + new java.util.Date(currentTime).toString()); LogUtil.d(TAG, "Initial trigger time: " + new java.util.Date(triggerAtMillis).toString()); - - if (currentTime > triggerAtMillis) { + + if (triggerAtMillis <= currentTime) { calendar.add(Calendar.DAY_OF_YEAR, 1); triggerAtMillis = calendar.getTimeInMillis(); - LogUtil.d(TAG, "Trigger time is in the past, scheduling for next day: " + new java.util.Date(triggerAtMillis).toString()); + LogUtil.d(TAG, "Updated trigger time for NEXT DAY: " + new java.util.Date(triggerAtMillis).toString()); + } else { + LogUtil.d(TAG, "Scheduling alarm for TODAY: " + new java.util.Date(triggerAtMillis).toString()); } Intent intent = new Intent(requireContext(), AutoAlarmReceiver.class); @@ -294,50 +297,33 @@ public class TMSProcessFragment extends DataBindingFragment { ); try { - // For Android 12+ (API 31+), we need to handle exact alarms differently - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { - // Check if the app can schedule exact alarms - if (alarmManager.canScheduleExactAlarms()) { - alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, AlarmManager.INTERVAL_DAY, pendingIntent); - LogUtil.d(TAG, "Auto settlement alarm scheduled successfully for: " + new java.util.Date(triggerAtMillis).toString()); + alarmManager.cancel(pendingIntent); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + if (alarmManager.canScheduleExactAlarms()) { + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); + } else { + alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); + } } else { - LogUtil.d(TAG, "App doesn't have permission to schedule exact alarms. Using inexact alarm."); - // Use inexact alarm as fallback - alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, AlarmManager.INTERVAL_DAY, pendingIntent); - LogUtil.d(TAG, "Auto settlement inexact alarm scheduled for: " + new java.util.Date(triggerAtMillis).toString()); + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); } } else { - // For older Android versions - alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, AlarmManager.INTERVAL_DAY, pendingIntent); - LogUtil.d(TAG, "Auto settlement alarm scheduled successfully for: " + new java.util.Date(triggerAtMillis).toString()); + alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); } - // Also try to schedule a test alarm 30 seconds from now for debugging - long testTriggerTime = currentTime + 30000; // 30 seconds from now - Intent testIntent = new Intent(requireContext(), AutoAlarmReceiver.class); - testIntent.putExtra("TEST_ALARM", true); - PendingIntent testPendingIntent = PendingIntent.getBroadcast( - requireContext(), - 1002, - testIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); - - // Schedule test alarm - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { - if (alarmManager.canScheduleExactAlarms()) { - alarmManager.setExact(AlarmManager.RTC_WAKEUP, testTriggerTime, testPendingIntent); - } else { - alarmManager.set(AlarmManager.RTC_WAKEUP, testTriggerTime, testPendingIntent); - } - } else { - alarmManager.set(AlarmManager.RTC_WAKEUP, testTriggerTime, testPendingIntent); - } - LogUtil.d(TAG, "Test alarm scheduled for: " + new java.util.Date(testTriggerTime).toString()); + long hoursUntilTrigger = (triggerAtMillis - currentTime) / 1000 / 60 / 60; + long minutesUntilTrigger = ((triggerAtMillis - currentTime) / 1000 / 60) % 60; + LogUtil.d(TAG, "Auto settlement alarm setup completed. Next occurrence: " + new Date(triggerAtMillis).toString()); + LogUtil.d(TAG, "Time until next trigger: " + hoursUntilTrigger + " hours, " + minutesUntilTrigger + " minutes"); } catch (Exception e) { - LogUtil.d(TAG, "Error scheduling alarm: " + e.getMessage()); + LogUtil.e(TAG, "Error scheduling main alarm: " + e.getMessage()); e.printStackTrace(); + + LogUtil.e(TAG, "Alarm scheduling failed - Check if app has SCHEDULE_EXACT_ALARM permission"); + LogUtil.e(TAG, "Failed alarm details - Target time: " + new Date(triggerAtMillis).toString()); } } diff --git a/paylibs/src/main/java/com/utsmyanmar/paylibs/print/printx/BaseXPrint.java b/paylibs/src/main/java/com/utsmyanmar/paylibs/print/printx/BaseXPrint.java index 1af53aa..70310af 100644 --- a/paylibs/src/main/java/com/utsmyanmar/paylibs/print/printx/BaseXPrint.java +++ b/paylibs/src/main/java/com/utsmyanmar/paylibs/print/printx/BaseXPrint.java @@ -201,7 +201,7 @@ public abstract class BaseXPrint { } protected void dashBreakEnding() { - printer.appendPrnStr("--------X----------X----------", fontNormal, AlignEnum.LEFT,false); + printer.appendPrnStr("--------X----------X---------", fontNormal, AlignEnum.LEFT,false); }